wizz-method 2.1.1 → 2.3.0
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.
- package/README.md +62 -62
- package/SECURITY.md +15 -16
- package/THIRD-PARTY-NOTICES.md +49 -49
- package/package.json +1 -1
- package/src/bmm-skills/1-analysis/wizz-agent-analyst/customize.toml +1 -1
- package/src/bmm-skills/2-plan-workflows/wizz-agent-pm/customize.toml +1 -1
- package/src/bmm-skills/2-plan-workflows/wizz-agent-ux-designer/customize.toml +1 -1
- package/src/bmm-skills/3-solutioning/wizz-agent-architect/customize.toml +1 -1
- package/src/bmm-skills/4-implementation/wizz-agent-dev/customize.toml +1 -1
- package/src/bmm-skills/4-implementation/wizz-code-review/SKILL.md +2 -2
- package/src/bmm-skills/4-implementation/wizz-code-review/customize.toml +102 -8
- package/src/bmm-skills/4-implementation/wizz-code-review/references/review-economy.md +75 -0
- package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-01-gather-context.md +29 -15
- package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-02-review.md +13 -10
- package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-03-triage.md +11 -5
- package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-04-present.md +4 -0
- package/src/bmm-skills/4-implementation/wizz-quick-dev/customize.toml +103 -7
- package/src/bmm-skills/4-implementation/wizz-quick-dev/references/review-economy.md +75 -0
- package/src/bmm-skills/4-implementation/wizz-quick-dev/step-04-review.md +43 -18
- package/src/bmm-skills/4-implementation/wizz-quick-dev/step-05-present.md +4 -0
- package/src/bmm-skills/4-implementation/wizz-quick-dev/step-oneshot.md +2 -2
- package/src/core-skills/_shared/handoff-protocol.md +72 -71
- package/src/core-skills/wizz-review-adversarial-general/SKILL.md +1 -1
- package/src/modules/lowticket/skills/wizz-lowticket-funil/references/copy-das-paginas.md +39 -39
- package/src/modules/lowticket/skills/wizz-lowticket-funil/references/mapa-do-funil.md +23 -23
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/CATALOGO.md +72 -72
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/01-principios.md +10 -10
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/02-diagnostico-funil.md +50 -50
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/03-campanha-bidcap.md +68 -68
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/04-criativos.md +44 -44
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/04a-criativos-ia.md +77 -77
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/04b-criativos-formatos.md +121 -121
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/05-oferta.md +35 -35
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/06-pagina-vendas.md +122 -122
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/06a-instagram.md +93 -93
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/06b-player-vsl.md +57 -57
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/07-funil-upsell.md +83 -83
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/07a-copy-humanizacao.md +61 -61
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/08-pesquisa-publico.md +11 -11
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/09-especialista-coproducao.md +26 -26
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/10-levantar-caixa.md +29 -29
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/11-checklist-roi.md +22 -22
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/12-mineracao-ofertas.md +80 -80
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/12a-mineracao-veredito.md +72 -72
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/12b-fabrica-de-ofertas.md +63 -63
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/12c-anatomia-low-ticket.md +71 -71
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/13-benchmarks-vsl.md +6 -6
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/14-vocabulario.md +53 -51
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/15-como-estender.md +26 -26
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/17-rastreamento.md +76 -76
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/18-copy-desejo-oferta.md +149 -149
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/INDEX.md +45 -45
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/guardrails.md +45 -45
- package/src/modules/lowticket/skills/wizz-lowticket-metodologia/knowledge/pontos-em-aberto.md +64 -66
- package/src/modules/lowticket/skills/wizz-lowticket-minerador/references/rubrica.md +63 -63
- package/src/modules/lowticket/skills/wizz-lowticket-pagina/references/metas-e-medicao.md +23 -23
- package/src/modules/lowticket/skills/wizz-lowticket-pagina/references/padroes-de-codigo.md +50 -50
- package/src/modules/lowticket/skills/wizz-lowticket-trafego/references/formato-do-diagnostico.md +9 -9
- package/src/modules/lowticket/skills/wizz-lowticket-trafego/references/reguas.md +42 -42
- package/src/modules/wizz/README.md +27 -27
- package/src/modules/wizz/_shared/cerebro.md +37 -37
- package/src/modules/wizz/_shared/communication-rules.md +15 -15
- package/src/modules/wizz/_shared/despacho-barato.md +6 -6
- package/src/modules/wizz/_shared/encerramento.md +23 -23
- package/src/modules/wizz/_shared/model-ladder.md +29 -28
- package/src/modules/wizz/_shared/planning-gate.md +30 -30
- package/src/modules/wizz/_shared/token-economy.md +28 -28
- package/src/modules/wizz/_shared/video-pipeline.md +42 -42
- package/src/modules/wizz/agents/wizz-ads/SKILL.md +16 -16
- package/src/modules/wizz/agents/wizz-ads/customize.toml +21 -21
- package/src/modules/wizz/agents/wizz-copy/SKILL.md +18 -18
- package/src/modules/wizz/agents/wizz-copy/customize.toml +24 -24
- package/src/modules/wizz/agents/wizz-designer/SKILL.md +24 -24
- package/src/modules/wizz/agents/wizz-designer/customize.toml +29 -29
- package/src/modules/wizz/agents/wizz-growth/SKILL.md +20 -20
- package/src/modules/wizz/agents/wizz-growth/customize.toml +34 -34
- package/src/modules/wizz/agents/wizz-maestro/SKILL.md +63 -63
- package/src/modules/wizz/agents/wizz-maestro/customize.toml +38 -38
- package/src/modules/wizz/agents/wizz-memoria/SKILL.md +19 -19
- package/src/modules/wizz/agents/wizz-memoria/customize.toml +24 -24
- package/src/modules/wizz/agents/wizz-qa/SKILL.md +22 -22
- package/src/modules/wizz/agents/wizz-qa/customize.toml +29 -29
- package/src/modules/wizz/agents/wizz-seo/SKILL.md +19 -19
- package/src/modules/wizz/agents/wizz-seo/customize.toml +27 -27
- package/src/modules/wizz/agents/wizz-social/SKILL.md +53 -53
- package/src/modules/wizz/agents/wizz-social/customize.toml +37 -37
- package/src/modules/wizz/agents/wizz-social/knowledge/analise-imagem-referencia.md +20 -20
- package/src/modules/wizz/agents/wizz-social/knowledge/biblioteca-prompts-imagem.md +71 -71
- package/src/modules/wizz/agents/wizz-social/knowledge/blueprint-roteiro-viral.md +196 -196
- package/src/modules/wizz/agents/wizz-social/knowledge/legenda-instagram.md +32 -32
- package/src/modules/wizz/agents/wizz-social/knowledge/prompts-imagem-video.md +48 -48
- package/src/modules/wizz/module.yaml +28 -28
- package/src/modules/wizz/overrides/wizz-agent-analyst.toml +17 -17
- package/src/modules/wizz/overrides/wizz-agent-architect.toml +21 -21
- package/src/modules/wizz/overrides/wizz-agent-dev.toml +20 -20
- package/src/modules/wizz/overrides/wizz-agent-pm.toml +11 -11
- package/src/modules/wizz/overrides/wizz-agent-tech-writer.toml +14 -14
- package/src/modules/wizz/overrides/wizz-agent-ux-designer.toml +14 -14
- package/src/modules/wizz/subagents/codex/wizz-exec-haiku.toml +17 -17
- package/src/modules/wizz/subagents/codex/wizz-exec-opus.toml +19 -19
- package/src/modules/wizz/subagents/codex/wizz-exec-review.toml +18 -17
- package/src/modules/wizz/subagents/codex/wizz-exec-sonnet.toml +18 -18
- package/src/modules/wizz/subagents/gemini/wizz-exec-haiku.md +17 -17
- package/src/modules/wizz/subagents/gemini/wizz-exec-opus.md +19 -19
- package/src/modules/wizz/subagents/gemini/wizz-exec-review.md +18 -17
- package/src/modules/wizz/subagents/gemini/wizz-exec-sonnet.md +18 -18
- package/src/modules/wizz/subagents/opencode/wizz-exec-haiku.md +17 -17
- package/src/modules/wizz/subagents/opencode/wizz-exec-opus.md +19 -19
- package/src/modules/wizz/subagents/opencode/wizz-exec-review.md +18 -17
- package/src/modules/wizz/subagents/opencode/wizz-exec-sonnet.md +18 -18
- package/src/modules/wizz/subagents/spec.yaml +84 -82
- package/src/modules/wizz/subagents/wizz-exec-haiku.md +17 -17
- package/src/modules/wizz/subagents/wizz-exec-opus.md +19 -19
- package/src/modules/wizz/subagents/wizz-exec-review.md +18 -17
- package/src/modules/wizz/subagents/wizz-exec-sonnet.md +18 -18
- package/src/skills-lib/cerebro/SKILL.md +61 -61
- package/src/skills-lib/cerebro/references/comandos-pesquisa-e-memoria.md +46 -46
- package/src/skills-lib/cerebro/references/comandos-rotina-e-captura.md +65 -65
- package/src/skills-lib/cerebro/references/comandos-sessao.md +74 -74
- package/src/skills-lib/ctc-align/SKILL.md +37 -37
- package/src/skills-lib/find-skills/SKILL.md +19 -19
- package/src/skills-lib/pesquisa-de-publico-do-piva/README.md +33 -33
- package/src/skills-lib/pesquisa-de-publico-do-piva/SKILL.md +42 -42
- package/src/skills-lib/pesquisa-de-publico-do-piva/prompts.md +20 -20
- package/src/skills-lib/references/premium-agency-rules.md +63 -63
- package/src/skills-lib/security-audit-pentest/SKILL.md +31 -31
- package/src/skills-lib/security-audit-pentest/references/stage-1-validacao-entrada.md +37 -37
- package/src/skills-lib/security-audit-pentest/references/stage-2-autorizacao.md +61 -61
- package/src/skills-lib/security-audit-pentest/references/stage-3-abuso-volume.md +37 -37
- package/src/skills-lib/security-audit-pentest/references/stage-4-vazamento-resposta.md +52 -52
- package/src/skills-lib/site-launch-kit/SKILL.md +42 -42
- package/src/skills-lib/site-launch-kit/references/01-cta-principal.md +20 -20
- package/src/skills-lib/site-launch-kit/references/02-barra-fixa-mobile.md +24 -24
- package/src/skills-lib/site-launch-kit/references/03-tempo-de-resposta.md +21 -21
- package/src/skills-lib/site-launch-kit/references/04-prova-social.md +22 -22
- package/src/skills-lib/site-launch-kit/references/05-faq-decisao.md +28 -28
- package/src/skills-lib/site-launch-kit/references/06-imagens-reais.md +27 -27
- package/src/skills-lib/site-launch-kit/references/07-endereco-como-chegar.md +25 -25
- package/src/skills-lib/site-launch-kit/references/08-titles.md +30 -30
- package/src/skills-lib/site-launch-kit/references/09-open-graph.md +23 -23
- package/src/skills-lib/site-launch-kit/references/10-breadcrumbs.md +23 -23
- package/src/skills-lib/site-launch-kit/references/11-alt-text.md +28 -28
- package/src/skills-lib/site-launch-kit/references/12-schema-local.md +22 -22
- package/src/skills-lib/site-launch-kit/references/13-robots-sitemap.md +26 -26
- package/src/skills-lib/site-launch-kit/references/14-privacidade-termos.md +27 -27
- package/src/skills-lib/site-launch-kit/references/15-medicao.md +23 -23
- package/src/skills-lib/wizz-ai-product-design/SKILL.md +70 -70
- package/src/skills-lib/wizz-auth-and-secrets/SKILL.md +78 -78
- package/src/skills-lib/wizz-caching-and-queues/SKILL.md +40 -40
- package/src/skills-lib/wizz-cloud-and-infra/SKILL.md +22 -22
- package/src/skills-lib/wizz-database-and-deps/SKILL.md +52 -52
- package/src/skills-lib/wizz-database-scaling/SKILL.md +31 -31
- package/src/skills-lib/wizz-desktop-security/SKILL.md +49 -49
- package/src/skills-lib/wizz-implementation-planner/SKILL.md +56 -56
- package/src/skills-lib/wizz-implementation-planner/references/folder-structure-and-dependencies.md +14 -14
- package/src/skills-lib/wizz-implementation-planner/references/implementation-phases.md +15 -15
- package/src/skills-lib/wizz-implementation-planner/references/implementation-types.md +9 -9
- package/src/skills-lib/wizz-implementation-planner/references/motion-path-implementation.md +19 -19
- package/src/skills-lib/wizz-implementation-planner/references/performance-plan.md +27 -27
- package/src/skills-lib/wizz-infrastructure/SKILL.md +21 -21
- package/src/skills-lib/wizz-inicio-de-projeto/SKILL.md +63 -63
- package/src/skills-lib/wizz-inicio-de-projeto/references/implementacao-api-forms.md +27 -27
- package/src/skills-lib/wizz-inicio-de-projeto/references/padroes-arquiteturais-e-pastas.md +31 -31
- package/src/skills-lib/wizz-inicio-de-projeto/references/seguranca-e-regras-codigo.md +21 -21
- package/src/skills-lib/wizz-launch-readiness/SKILL.md +61 -61
- package/src/skills-lib/wizz-launch-readiness/references/01-tecnico-build.md +16 -16
- package/src/skills-lib/wizz-launch-readiness/references/02-seguranca.md +16 -16
- package/src/skills-lib/wizz-launch-readiness/references/03-seo-descoberta.md +16 -16
- package/src/skills-lib/wizz-launch-readiness/references/04-analytics-medicao.md +15 -15
- package/src/skills-lib/wizz-launch-readiness/references/05-conteudo-prova-social.md +15 -15
- package/src/skills-lib/wizz-launch-readiness/references/06-legal-lgpd.md +15 -15
- package/src/skills-lib/wizz-launch-readiness/references/07-infra-deploy-rollback.md +16 -16
- package/src/skills-lib/wizz-launch-readiness/references/08-fusao-priorizacao.md +19 -19
- package/src/skills-lib/wizz-launch-readiness/references/09-persistencia-project-context.md +26 -26
- package/src/skills-lib/wizz-motion-3d-director/SKILL.md +70 -70
- package/src/skills-lib/wizz-motion-3d-director/references/image-to-video-workflow.md +34 -35
- package/src/skills-lib/wizz-motion-3d-director/references/motion-paths.md +70 -70
- package/src/skills-lib/wizz-motion-3d-director/references/performance-and-handoff.md +17 -17
- package/src/skills-lib/wizz-offer-forge/README.md +9 -9
- package/src/skills-lib/wizz-offer-forge/SKILL.md +64 -64
- package/src/skills-lib/wizz-offer-forge/references/empilhamento-valor.md +17 -17
- package/src/skills-lib/wizz-offer-forge/references/garantia-reversa.md +16 -16
- package/src/skills-lib/wizz-offer-forge/references/mecanismo-unico.md +19 -19
- package/src/skills-lib/wizz-offer-forge/references/ponte-trafego.md +17 -17
- package/src/skills-lib/wizz-premium-landing-ui-researcher/SKILL.md +36 -36
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/audit-protocol.md +81 -82
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/component-sources.md +105 -105
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/core-goal.md +39 -39
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/dashboard-and-portfolio-modes.md +49 -50
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/handoffs.md +33 -34
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/landing-page-strategy.md +141 -142
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/mandatory-process.md +24 -24
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/output-format-and-quality.md +73 -73
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/prompt-templates.md +4 -4
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/site-levels.md +282 -283
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/source-first-protocol.md +39 -39
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/source-links.md +8 -8
- package/src/skills-lib/wizz-premium-landing-ui-researcher/references/stack-and-visual-direction.md +63 -64
- package/src/skills-lib/wizz-router/SKILL.md +38 -38
- package/src/skills-lib/wizz-router/references/auditoria-360.md +12 -12
- package/src/skills-lib/wizz-router/references/routing-table-flat.md +80 -80
- package/src/skills-lib/wizz-tracking-audit/SKILL.md +48 -48
- package/src/skills-lib/wizz-tracking-audit/references/modo-diagnostico.md +43 -43
- package/src/skills-lib/wizz-tracking-audit/references/modo-implantacao.md +59 -59
- package/src/skills-lib/wizz-tracking-audit/references/utm-e-atribuicao.md +35 -35
- package/src/skills-lib/wizz-web-security/SKILL.md +35 -35
- package/src/skills-lib/wizz-web-security/references/checklist-deploy.md +50 -50
- package/src/skills-lib/wizz-web-security/references/headers-rate-limit-cors.md +33 -33
- package/src/skills-lib/wizz-web-security/references/owasp-top5-detalhado.md +36 -36
- package/tools/hooks/session-rules.js +14 -11
- package/tools/installer/core/resolved-customizations.js +58 -10
- package/tools/installer/core/resolved-schema.js +116 -18
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# Headers
|
|
1
|
+
# Security Headers, Rate Limiting, CORS, and Data Leakage
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Load this file when implementing/reviewing security headers, rate limiting, CORS, or an API's response shape (over-fetch/PII).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Mandatory security headers
|
|
6
6
|
|
|
7
|
-
`X-Frame-Options: DENY` + `frame-ancestors 'none'`
|
|
7
|
+
`X-Frame-Options: DENY` + `frame-ancestors 'none'` in the CSP kills **clickjacking** (the site can't be embedded in an `<iframe>` from another domain). A 1-minute fix, always present.
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
Content-Security-Policy: default-src 'self'; frame-ancestors 'none'
|
|
@@ -15,7 +15,7 @@ Referrer-Policy: strict-origin-when-cross-origin
|
|
|
15
15
|
Permissions-Policy: camera=(), microphone=(), geolocation=()
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
How to add it in Next.js (`next.config.ts`):
|
|
19
19
|
```ts
|
|
20
20
|
async headers() {
|
|
21
21
|
return [
|
|
@@ -44,46 +44,46 @@ async headers() {
|
|
|
44
44
|
},
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
> **
|
|
47
|
+
> **Careful:** a wrong CSP breaks the app. Always test in dev before deploying. Adjust the domains (`script-src`, `connect-src`) to the project's real services.
|
|
48
48
|
|
|
49
49
|
## Rate Limiting
|
|
50
|
-
- Login:
|
|
51
|
-
- APIs
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
- Next.js: `@upstash/ratelimit`
|
|
55
|
-
- Fail closed:
|
|
56
|
-
-
|
|
57
|
-
-
|
|
50
|
+
- Login: limit per IP **and** per account (IP-only doesn't protect against distributed brute force; account-only doesn't protect against scanning many emails)
|
|
51
|
+
- Public APIs: limit per IP and per token
|
|
52
|
+
- Password reset endpoints: especially restrictive
|
|
53
|
+
- Billing endpoints (checkout, portal): always with rate limiting
|
|
54
|
+
- Next.js: `@upstash/ratelimit` in the route handler, or Vercel's WAF for per-route rules
|
|
55
|
+
- Fail closed: if the rate-limit backend goes down (e.g. Redis is out), write routes should deny the request, not let it through
|
|
56
|
+
- Token/secret comparisons: `crypto.timingSafeEqual`, never `!==` (details in wizz-auth-and-secrets)
|
|
57
|
+
- No rate limit = free brute force. Add lockout/CAPTCHA after N failed login attempts
|
|
58
58
|
|
|
59
|
-
###
|
|
60
|
-
|
|
61
|
-
- Vercel: use `x-forwarded-for` (
|
|
62
|
-
-
|
|
59
|
+
### IP origin behind a proxy/CDN
|
|
60
|
+
If the app runs behind Vercel/Cloudflare/any proxy, the socket's IP is the proxy's, not the client's. Reading the raw socket makes **the entire world count as a single IP** and the rate limit effectively stops existing.
|
|
61
|
+
- Vercel: use `x-forwarded-for` (or `req.ip` in the runtime that already resolves it). Take the **first** IP in the list, but only trust the chain that comes from your own proxy.
|
|
62
|
+
- Never trust a raw `x-forwarded-for` coming from a non-proxy origin (the client can spoof the header). Configure the rate-limit library for the correct number of proxies in front.
|
|
63
63
|
|
|
64
|
-
###
|
|
65
|
-
|
|
64
|
+
### Per-call cost (a route that spends real money)
|
|
65
|
+
Every route that calls a language model, sends an SMS, sends an email, processes a file, or hits a paid third-party API needs rate limiting **and** a per-account quota. An AI agent route exposed without a limiter is critical even without leaking any data at all: 10,000 calls in a loop turn into a bill of hundreds to thousands of dollars. Estimate the unit cost and multiply by 10k when assessing the route.
|
|
66
66
|
|
|
67
|
-
###
|
|
68
|
-
|
|
67
|
+
### Extraction via pagination
|
|
68
|
+
A route that returns one record at a time and, called in sequence, hands over the entire database. Block it with: an owner filter in the query (the same as for IDOR), a maximum page size, and scan detection (many sequential IDs from the same client).
|
|
69
69
|
|
|
70
70
|
## CORS
|
|
71
|
-
- **
|
|
72
|
-
- Use
|
|
73
|
-
- `Access-Control-Allow-Credentials: true`
|
|
74
|
-
-
|
|
71
|
+
- **Never reflect the received `Origin` header** back in `Access-Control-Allow-Origin` (this lets any site call your API with credentials)
|
|
72
|
+
- Use an explicit **allowlist** of origins: compare the received `Origin` against the list and only then echo back that exact origin's value
|
|
73
|
+
- `Access-Control-Allow-Credentials: true` NEVER together with `Access-Control-Allow-Origin: *`
|
|
74
|
+
- Default = deny: unknown origin → no CORS headers (the browser blocks it on its own)
|
|
75
75
|
```ts
|
|
76
76
|
const ALLOWED = new Set(["https://app.exemplo.com", "https://exemplo.com"])
|
|
77
77
|
const origin = req.headers.get("origin")
|
|
78
78
|
if (origin && ALLOWED.has(origin)) {
|
|
79
|
-
res.headers.set("Access-Control-Allow-Origin", origin) //
|
|
79
|
+
res.headers.set("Access-Control-Allow-Origin", origin) // exact value, never "*" with credentials
|
|
80
80
|
res.headers.set("Vary", "Origin")
|
|
81
81
|
}
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
##
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
- Supabase: `.select('id, name, email')
|
|
88
|
-
-
|
|
89
|
-
-
|
|
84
|
+
## Data leakage in the response (over-fetch / PII)
|
|
85
|
+
- Return **only the fields the screen needs**: build an output DTO/allowlist, never return the whole database row
|
|
86
|
+
- NEVER include `password`, `password_hash`, tokens, secrets, or internal columns in an API response (this is the most serious failure on the PII list)
|
|
87
|
+
- Supabase: explicit `.select('id, name, email')`, never `.select('*')` on an endpoint that responds to the client
|
|
88
|
+
- Logged-in user object: serialize it through a mapper that only exposes public fields
|
|
89
|
+
- Errors: don't leak stack traces, SQL, or table names to the client. Generic message in the response, detail only in the server log
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# OWASP —
|
|
1
|
+
# OWASP — Detail of the 5 most common categories
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Load this file when digging deeper into one of the failures from the quick triage (sections 1-5 referenced in the SKILL.md table).
|
|
4
4
|
|
|
5
5
|
## 1. Broken Access Control
|
|
6
|
-
-
|
|
7
|
-
- IDOR (Insecure Direct Object Reference):
|
|
8
|
-
-
|
|
9
|
-
- Next.js: middleware
|
|
6
|
+
- Check permissions on the server, never only on the frontend
|
|
7
|
+
- IDOR (Insecure Direct Object Reference): validate that the user has access to the resource by ID
|
|
8
|
+
- Principle of least privilege on every route
|
|
9
|
+
- Next.js: middleware isn't the only barrier. Re-check auth inside the route handler, Server Action, or page (a middleware bypass has already had a CVE)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Anti-IDOR example (Next.js + Clerk + Supabase):
|
|
12
12
|
```ts
|
|
13
13
|
// app/api/projects/[id]/route.ts
|
|
14
14
|
import { auth } from "@clerk/nextjs/server"
|
|
@@ -21,63 +21,63 @@ export async function GET(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
21
21
|
.from("projects")
|
|
22
22
|
.select("id, name")
|
|
23
23
|
.eq("id", id)
|
|
24
|
-
.eq("user_id", userId) //
|
|
24
|
+
.eq("user_id", userId) // ownership verified in the query, in addition to RLS
|
|
25
25
|
.single()
|
|
26
|
-
if (!data) return new Response("Not found", { status: 404 }) // 404,
|
|
26
|
+
if (!data) return new Response("Not found", { status: 404 }) // 404, not 403 (doesn't confirm the resource exists)
|
|
27
27
|
return Response.json(data)
|
|
28
28
|
}
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
### RLS: policy
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
31
|
+
### RLS: a permissive policy is worse than no policy
|
|
32
|
+
Read the versioned migration/policy files and check:
|
|
33
|
+
- Does every domain table have `ENABLE ROW LEVEL SECURITY`?
|
|
34
|
+
- Is no policy permissive: `USING (true)` or `WITH CHECK (true)` opens it to everyone, but the Supabase dashboard shows the lock as "active." Always tie it to the owner: `USING (auth.uid() = user_id)`.
|
|
35
|
+
- A table created in a recent migration **without** a corresponding policy = exposed.
|
|
36
36
|
|
|
37
37
|
```sql
|
|
38
|
-
--
|
|
38
|
+
-- WRONG: shows RLS active in the dashboard, but opens it to anyone
|
|
39
39
|
create policy "read" on projects for select using (true);
|
|
40
40
|
|
|
41
|
-
--
|
|
41
|
+
-- RIGHT: ties it to the owner
|
|
42
42
|
alter table projects enable row level security;
|
|
43
43
|
create policy "own rows" on projects
|
|
44
44
|
for select using (auth.uid() = user_id);
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
> RLS
|
|
47
|
+
> Real RLS is database state, not a file. What's in the repo is a starting point: confirm the real state in the dashboard/`\d+` of Postgres.
|
|
48
48
|
|
|
49
|
-
## 6. Mass Assignment
|
|
50
|
-
|
|
49
|
+
## 6. Mass Assignment
|
|
50
|
+
Passing the entire request body through to the database (`spread` of the object, `update`/`create` with the full payload) lets an attacker set any column, even ones not in the form. Classic targets: `role`, `is_admin`, `plan`, `balance`, `payment_status`, `user_id`/`owner_id`.
|
|
51
51
|
|
|
52
52
|
```ts
|
|
53
|
-
//
|
|
53
|
+
// WRONG: the client sends { name: "x", role: "admin" } and becomes admin
|
|
54
54
|
await supabase.from("users").update({ ...body }).eq("id", userId)
|
|
55
55
|
|
|
56
|
-
//
|
|
57
|
-
const { name, bio } = updateSchema.parse(body) // Zod
|
|
56
|
+
// RIGHT: explicit allowlist, only the form's fields
|
|
57
|
+
const { name, bio } = updateSchema.parse(body) // Zod with only the allowed fields
|
|
58
58
|
await supabase.from("users").update({ name, bio }).eq("id", userId)
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Rule: **never** `...body` / `...req.body` in a write. Always destructure (or validate with a schema that only contains the editable fields). Role, permission, plan, balance, payment status, and owner id fields never come from the client.
|
|
62
62
|
|
|
63
63
|
## 2. Cryptographic Failures
|
|
64
|
-
- HTTPS
|
|
65
|
-
- HSTS header
|
|
66
|
-
-
|
|
64
|
+
- HTTPS everywhere, no exceptions
|
|
65
|
+
- HSTS header with includeSubDomains
|
|
66
|
+
- Don't expose sensitive data in URLs (query strings end up in logs)
|
|
67
67
|
|
|
68
68
|
## 3. Injection
|
|
69
|
-
- SQL:
|
|
69
|
+
- SQL: always parameterized queries
|
|
70
70
|
- XSS: sanitize outputs, use Content-Security-Policy
|
|
71
|
-
- Command injection:
|
|
71
|
+
- Command injection: never execute user input as a shell command
|
|
72
72
|
|
|
73
73
|
## 4. Cross-Site Scripting (XSS)
|
|
74
|
-
- Escape HTML
|
|
75
|
-
- React/Next.js
|
|
76
|
-
- URLs
|
|
77
|
-
- Content-Security-Policy:
|
|
78
|
-
- HttpOnly
|
|
74
|
+
- Escape HTML on every piece of user-generated output
|
|
75
|
+
- React/Next.js escapes JSX by default. The real risk is in `dangerouslySetInnerHTML`: sanitize with DOMPurify first (or don't use it)
|
|
76
|
+
- User-supplied URLs in `href`/`src`: block `javascript:` and `data:` schemes (validate that it starts with `https://` or a relative path)
|
|
77
|
+
- Content-Security-Policy: block inline scripts
|
|
78
|
+
- HttpOnly and Secure flags on session cookies
|
|
79
79
|
|
|
80
80
|
## 5. CSRF
|
|
81
|
-
- SameSite=Strict
|
|
82
|
-
- CSRF token
|
|
83
|
-
-
|
|
81
|
+
- SameSite=Strict or Lax on session cookies
|
|
82
|
+
- CSRF token on state-changing forms
|
|
83
|
+
- Check Origin/Referer on sensitive requests
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Três responsabilidades, um processo só (SessionStart roda 1x por sessão):
|
|
6
6
|
//
|
|
7
|
-
// 1.
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// 1. Communication rules, injected ONCE per session (before: no-narration-enforce
|
|
8
|
+
// repeated ~80 tokens on EVERY prompt via UserPromptSubmit). Same content,
|
|
9
|
+
// fixed cost per session.
|
|
10
10
|
//
|
|
11
11
|
// 2. Feature ativa: resolve o `active_feature` SOZINHO, sem o usuário
|
|
12
12
|
// precisar invocar `wizz-set-feature` e responder duas perguntas. Ordem:
|
|
@@ -26,15 +26,18 @@ const fs = require('node:fs');
|
|
|
26
26
|
const os = require('node:os');
|
|
27
27
|
const path = require('node:path');
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
// src/modules/wizz/_shared/communication-rules.md;
|
|
31
|
-
//
|
|
32
|
-
//
|
|
29
|
+
// Compressed and INTENTIONAL copy of the communication rule. The source is
|
|
30
|
+
// src/modules/wizz/_shared/communication-rules.md; this hook needs to be
|
|
31
|
+
// self-contained because it runs from ~/.claude/hooks, far from the repo. Edit
|
|
32
|
+
// it there, edit it here too: test/test-feature-context.js fails if the two diverge.
|
|
33
33
|
const RULES =
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
34
|
+
'COMMUNICATION RULE (permanent, non-negotiable): do not narrate the step-by-step ' +
|
|
35
|
+
'while working; pause and ask ONLY for a user decision or irreversible risk; ' +
|
|
36
|
+
'always close with a short summary in bullet points, plain language, no jargon; ' +
|
|
37
|
+
'no em-dashes only in text a third party reads (public page, copy, UI), ' +
|
|
38
|
+
'code comments, commits, and internal notes are exempt from this check; ' +
|
|
39
|
+
'concise communication by default, expand only on request; ' +
|
|
40
|
+
'reply to the user in Brazilian Portuguese.';
|
|
38
41
|
|
|
39
42
|
// Branches que NUNCA viram feature: são linha principal, não trabalho nomeado.
|
|
40
43
|
const TRUNK = new Set(['main', 'master', 'develop', 'dev', 'staging', 'stage', 'prod', 'production', 'trunk', 'default', 'release']);
|
|
@@ -78,11 +78,35 @@ function detectKeyedMergeField(items) {
|
|
|
78
78
|
return null;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Warn when a team/user override REPLACES a base review layer (matched by
|
|
83
|
+
* `id`/`code`) but omits `tiers` or `max_calls` the base layer had.
|
|
84
|
+
* `mergeByKey` replaces the whole matched item — it never merges an
|
|
85
|
+
* override item field-by-field against its base counterpart (Python
|
|
86
|
+
* parity) — so dropping those fields silently loses the base layer's
|
|
87
|
+
* scaling: the layer now runs on every tier and/or with no call cap.
|
|
88
|
+
* No-op when `opts.warnings`/`opts.layerLabel` are not supplied, so callers
|
|
89
|
+
* that don't care about this (e.g. direct `deepMerge` calls in tests) pay
|
|
90
|
+
* nothing for it.
|
|
91
|
+
*/
|
|
92
|
+
function warnOverrideDropsLayerScaling({ warnings, layerLabel, key, matchedBase, item }) {
|
|
93
|
+
if (!warnings || !layerLabel) return;
|
|
94
|
+
const missing = ['tiers', 'max_calls'].filter((field) => hasOwn(matchedBase, field) && !hasOwn(item, field));
|
|
95
|
+
if (missing.length === 0) return;
|
|
96
|
+
const consequence = missing.map((field) => (field === 'tiers' ? 'every tier' : 'uncapped')).join(' and ');
|
|
97
|
+
warnings.push(
|
|
98
|
+
`${layerLabel} override: "${key}" replaces the whole base layer and omits ${missing.join(' and ')} — it now runs on ${consequence}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
81
102
|
/**
|
|
82
103
|
* Keyed merge: matching identifiers replace in place, new ones append.
|
|
83
104
|
* Non-table items in the override are appended (Python parity).
|
|
105
|
+
* @param {object} [opts] - `{ warnings, layerLabel }` to collect the
|
|
106
|
+
* dropped-scaling warning above; omit for warning-free merges (tests,
|
|
107
|
+
* internal recomputation).
|
|
84
108
|
*/
|
|
85
|
-
function mergeByKey(base, override, keyName) {
|
|
109
|
+
function mergeByKey(base, override, keyName, opts) {
|
|
86
110
|
const result = [];
|
|
87
111
|
const indexByKey = new Map();
|
|
88
112
|
for (const item of base) {
|
|
@@ -99,7 +123,9 @@ function mergeByKey(base, override, keyName) {
|
|
|
99
123
|
}
|
|
100
124
|
const key = item[keyName];
|
|
101
125
|
if (key !== undefined && key !== null && indexByKey.has(key)) {
|
|
102
|
-
|
|
126
|
+
const matchedIndex = indexByKey.get(key);
|
|
127
|
+
if (opts) warnOverrideDropsLayerScaling({ ...opts, key, matchedBase: result[matchedIndex], item });
|
|
128
|
+
result[matchedIndex] = { ...item };
|
|
103
129
|
} else {
|
|
104
130
|
if (key !== undefined && key !== null) indexByKey.set(key, result.length);
|
|
105
131
|
result.push({ ...item });
|
|
@@ -112,30 +138,33 @@ function mergeByKey(base, override, keyName) {
|
|
|
112
138
|
* Shape-aware array merge: keyed merge when every item (base + override)
|
|
113
139
|
* shares `code` or `id`, else append.
|
|
114
140
|
*/
|
|
115
|
-
function mergeArrays(base, override) {
|
|
141
|
+
function mergeArrays(base, override, opts) {
|
|
116
142
|
const baseArr = Array.isArray(base) ? base : [];
|
|
117
143
|
const overrideArr = Array.isArray(override) ? override : [];
|
|
118
144
|
const keyedField = detectKeyedMergeField([...baseArr, ...overrideArr]);
|
|
119
|
-
if (keyedField) return mergeByKey(baseArr, overrideArr, keyedField);
|
|
145
|
+
if (keyedField) return mergeByKey(baseArr, overrideArr, keyedField, opts);
|
|
120
146
|
return [...baseArr, ...overrideArr];
|
|
121
147
|
}
|
|
122
148
|
|
|
123
149
|
/**
|
|
124
150
|
* Structural deep merge. Tables deep-merge, arrays shape-merge,
|
|
125
151
|
* everything else: override wins. Python `deep_merge` parity.
|
|
152
|
+
* @param {object} [opts] - `{ warnings, layerLabel }`, threaded down to
|
|
153
|
+
* every `mergeByKey` call reached while walking `base`/`override`; see
|
|
154
|
+
* `warnOverrideDropsLayerScaling`.
|
|
126
155
|
*/
|
|
127
|
-
function deepMerge(base, override) {
|
|
156
|
+
function deepMerge(base, override, opts) {
|
|
128
157
|
if (isPlainObject(base) && isPlainObject(override)) {
|
|
129
158
|
const result = { ...base };
|
|
130
159
|
for (const [key, overVal] of Object.entries(override)) {
|
|
131
160
|
// hasOwn, not `in`: `'constructor' in result` is true via the
|
|
132
161
|
// prototype chain even when the base layer never declared it, which
|
|
133
162
|
// would deep-merge the override into Object's constructor function.
|
|
134
|
-
safeSet(result, key, hasOwn(result, key) ? deepMerge(result[key], overVal) : overVal);
|
|
163
|
+
safeSet(result, key, hasOwn(result, key) ? deepMerge(result[key], overVal, opts) : overVal);
|
|
135
164
|
}
|
|
136
165
|
return result;
|
|
137
166
|
}
|
|
138
|
-
if (Array.isArray(base) && Array.isArray(override)) return mergeArrays(base, override);
|
|
167
|
+
if (Array.isArray(base) && Array.isArray(override)) return mergeArrays(base, override, opts);
|
|
139
168
|
return override;
|
|
140
169
|
}
|
|
141
170
|
|
|
@@ -455,16 +484,35 @@ async function resolveSkillSnapshot({ customizePath, customDir, resolvedDir, pro
|
|
|
455
484
|
// layer is exempt (it owns the LLM-only fields and new experimental
|
|
456
485
|
// keys); a typo here is exactly the silent-merge the audit flagged.
|
|
457
486
|
// Warnings, never errors — the snapshot is still written (fail-open).
|
|
487
|
+
// findUnknownKeys() returns structured { path, kind, detail? } entries:
|
|
488
|
+
// `kind` says "unknown key" vs "invalid value" directly, so this loop
|
|
489
|
+
// never has to sniff the rendered path text (a quoted TOML key can itself
|
|
490
|
+
// contain a space, which broke the old `includes(' ')` heuristic).
|
|
458
491
|
for (const [layerName, layer] of [
|
|
459
492
|
['team', teamRaw],
|
|
460
493
|
['user', userRaw],
|
|
461
494
|
]) {
|
|
462
|
-
for (const
|
|
463
|
-
warnings.push(
|
|
495
|
+
for (const { path: keyPath, kind, detail } of findUnknownKeys(layer)) {
|
|
496
|
+
warnings.push(
|
|
497
|
+
kind === 'invalid'
|
|
498
|
+
? `${layerName} override: invalid ${keyPath}${detail ? ` (${detail})` : ''} — check customize.toml docs`
|
|
499
|
+
: `${layerName} override: unknown key "${keyPath}" (not a recognized workflow field — typo? check customize.toml docs)`,
|
|
500
|
+
);
|
|
464
501
|
}
|
|
465
502
|
}
|
|
466
503
|
|
|
467
|
-
|
|
504
|
+
// Audit item 24 follow-up: a team/user override that REPLACES a base
|
|
505
|
+
// review layer (matched by id/code) but drops the base's `tiers`/
|
|
506
|
+
// `max_calls` silently loses that scaling — see
|
|
507
|
+
// warnOverrideDropsLayerScaling. Two separate deepMerge calls (rather
|
|
508
|
+
// than the previous deepMerge(deepMerge(defaults, team), user)) so each
|
|
509
|
+
// one can be labeled with the layer that is doing the overriding; a user
|
|
510
|
+
// override is checked against the team-adjusted result, which is the
|
|
511
|
+
// layer it actually replaces.
|
|
512
|
+
const overrideWarnings = [];
|
|
513
|
+
const afterTeam = deepMerge(defaults, team, { warnings: overrideWarnings, layerLabel: 'team' });
|
|
514
|
+
const merged = deepMerge(afterTeam, user, { warnings: overrideWarnings, layerLabel: 'user' });
|
|
515
|
+
warnings.push(...overrideWarnings);
|
|
468
516
|
const body = `${JSON.stringify(merged, null, 2)}\n`;
|
|
469
517
|
// Atomic write via fs-native (tmp + rename), consistent with every other
|
|
470
518
|
// config file the installer writes.
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Extracted from `resolved-customizations.js` (2026-09-11) to keep that file
|
|
5
5
|
* under the 800-line cap. Validation only: nothing here writes, parses or
|
|
6
|
-
* merges — it takes ONE parsed layer and reports the keys no code
|
|
6
|
+
* merges — it takes ONE parsed layer and reports the keys/values no code
|
|
7
|
+
* consumes or that are structurally invalid.
|
|
7
8
|
*
|
|
8
9
|
* "additionalProperties: false" in spirit. The BASE layer (shipped,
|
|
9
10
|
* code-reviewed) is exempt: it may legitimately carry LLM-only fields
|
|
@@ -14,6 +15,12 @@
|
|
|
14
15
|
* is a warning (not an error: fail-open, the snapshot is still written).
|
|
15
16
|
* `include` is the documented extension point and is validated by
|
|
16
17
|
* `resolveIncludes`.
|
|
18
|
+
*
|
|
19
|
+
* Findings are structured `{ path, kind: 'unknown' | 'invalid', detail? }`
|
|
20
|
+
* entries — never plain strings. `kind` is how a caller tells "this key
|
|
21
|
+
* doesn't exist" from "this key exists but its value is wrong"; sniffing the
|
|
22
|
+
* rendered path text (e.g. for a space) breaks the moment a dotted path or a
|
|
23
|
+
* quoted TOML key legitimately contains one.
|
|
17
24
|
*/
|
|
18
25
|
|
|
19
26
|
const { isPlainObject } = require('./resolved-includes');
|
|
@@ -72,31 +79,56 @@ const WORKFLOW_KEYS = new Set([
|
|
|
72
79
|
// item keys are validated against REVIEW_LAYER_KEYS below.
|
|
73
80
|
'review_layers',
|
|
74
81
|
'oneshot_review_layers',
|
|
82
|
+
// review economy (review-economy.md): diff cleaning excludes/critical paths.
|
|
83
|
+
'review_exclude',
|
|
84
|
+
'review_critical_paths',
|
|
75
85
|
]);
|
|
76
86
|
const PARTY_MEMBER_KEYS = new Set(['code', 'name', 'icon', 'title', 'persona', 'capabilities', 'model']);
|
|
77
87
|
const PARTY_GROUP_KEYS = new Set(['id', 'name', 'scene', 'members', 'memory']);
|
|
78
|
-
// Exactly the keys the
|
|
79
|
-
// (src/bmm-skills/4-implementation/wizz-code-review + wizz-quick-dev)
|
|
80
|
-
|
|
88
|
+
// Exactly the keys the shipped `[[workflow.*review_layers]]` entries use
|
|
89
|
+
// (src/bmm-skills/4-implementation/wizz-code-review + wizz-quick-dev), plus
|
|
90
|
+
// `tiers`/`max_calls` for the review-economy tier scaling (review-economy.md).
|
|
91
|
+
const REVIEW_LAYER_KEYS = new Set(['id', 'name', 'instruction', 'when', 'tiers', 'max_calls']);
|
|
92
|
+
// The only valid values for a review_layers/oneshot_review_layers `tiers`
|
|
93
|
+
// entry (review-economy.md tier scaling). A typo (`"crtical"`) used to load
|
|
94
|
+
// silently and drop the layer on every tier — nothing ever matched it.
|
|
95
|
+
const VALID_REVIEW_TIERS = new Set(['small', 'normal', 'critical']);
|
|
81
96
|
// The only top-level keys any of the 47 shipped customize.toml carry. A
|
|
82
97
|
// top-level typo (`[workflw]`) or a stray scalar merged into silence before.
|
|
83
98
|
const TOP_LEVEL_KEYS = new Set(['workflow', 'agent', 'include']);
|
|
84
99
|
|
|
100
|
+
/** `value` is an array whose every element is a string. */
|
|
101
|
+
function isArrayOfStrings(value) {
|
|
102
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
103
|
+
}
|
|
104
|
+
|
|
85
105
|
/**
|
|
86
|
-
* Collect unknown
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
106
|
+
* Collect unknown/invalid entries under `[workflow]`: unknown fields by
|
|
107
|
+
* name, invalid values for the two review-economy string-array fields, and
|
|
108
|
+
* — for the arrays-of-tables we own (party members/groups, review layers) —
|
|
109
|
+
* the unknown keys and invalid values of each item.
|
|
110
|
+
*
|
|
111
|
+
* Every array-of-tables item is validated on its own, even when the array
|
|
112
|
+
* also holds non-object elements: a non-object item is flagged (invalid)
|
|
113
|
+
* instead of skipping validation for the whole array, which used to let a
|
|
114
|
+
* stray non-table entry hide typos in every OTHER entry.
|
|
115
|
+
* @returns {Array<{path: string, kind: 'unknown'|'invalid', detail?: string}>}
|
|
90
116
|
*/
|
|
91
117
|
function findUnknownWorkflowKeys(workflow) {
|
|
92
118
|
const unknown = [];
|
|
93
119
|
for (const [k2, v2] of Object.entries(workflow)) {
|
|
94
120
|
const wfPath = `workflow.${k2}`;
|
|
95
121
|
if (!WORKFLOW_KEYS.has(k2)) {
|
|
96
|
-
unknown.push(wfPath);
|
|
122
|
+
unknown.push({ path: wfPath, kind: 'unknown' });
|
|
97
123
|
continue;
|
|
98
124
|
}
|
|
99
|
-
if (
|
|
125
|
+
if (k2 === 'review_exclude' || k2 === 'review_critical_paths') {
|
|
126
|
+
if (!isArrayOfStrings(v2)) {
|
|
127
|
+
unknown.push({ path: wfPath, kind: 'invalid', detail: `expected an array of strings, got ${JSON.stringify(v2)}` });
|
|
128
|
+
}
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (!Array.isArray(v2) || v2.length === 0) continue;
|
|
100
132
|
const allowed =
|
|
101
133
|
k2 === 'party_members'
|
|
102
134
|
? PARTY_MEMBER_KEYS
|
|
@@ -106,30 +138,93 @@ function findUnknownWorkflowKeys(workflow) {
|
|
|
106
138
|
? REVIEW_LAYER_KEYS
|
|
107
139
|
: null;
|
|
108
140
|
if (!allowed) continue;
|
|
141
|
+
const isOneshot = k2 === 'oneshot_review_layers';
|
|
109
142
|
for (const item of v2) {
|
|
143
|
+
if (!isPlainObject(item)) {
|
|
144
|
+
unknown.push({ path: `${wfPath}[]`, kind: 'invalid', detail: `expected a table, got ${JSON.stringify(item)}` });
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
110
147
|
for (const k3 of Object.keys(item)) {
|
|
111
|
-
if (!allowed.has(k3)) unknown.push(`${wfPath}[].${k3}
|
|
148
|
+
if (!allowed.has(k3)) unknown.push({ path: `${wfPath}[].${k3}`, kind: 'unknown' });
|
|
112
149
|
}
|
|
150
|
+
if (allowed === REVIEW_LAYER_KEYS) unknown.push(...findInvalidReviewLayerValues(item, wfPath, isOneshot));
|
|
113
151
|
}
|
|
114
152
|
}
|
|
115
153
|
return unknown;
|
|
116
154
|
}
|
|
117
155
|
|
|
118
156
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* `
|
|
157
|
+
* Validate the VALUES of a review_layers/oneshot_review_layers item (item
|
|
158
|
+
* keys themselves are already checked by the caller against
|
|
159
|
+
* REVIEW_LAYER_KEYS). A typo in `tiers` (`"crtical"`), an empty `tiers`
|
|
160
|
+
* array, a non-positive `max_calls`, or a `tiers` entry on an
|
|
161
|
+
* `oneshot_review_layers` item (that step ignores tiers — it always runs)
|
|
162
|
+
* all load silently today: deepMerge applies them, nothing catches the
|
|
163
|
+
* mistake, the layer misbehaves. Warnings, same shape as unknown keys —
|
|
164
|
+
* fail-open, the snapshot is still written.
|
|
165
|
+
* @param {object} item
|
|
166
|
+
* @param {string} wfPath
|
|
167
|
+
* @param {boolean} isOneshot - true for an `oneshot_review_layers` item
|
|
168
|
+
* @returns {Array<{path: string, kind: 'invalid', detail: string}>}
|
|
169
|
+
*/
|
|
170
|
+
function findInvalidReviewLayerValues(item, wfPath, isOneshot) {
|
|
171
|
+
const invalid = [];
|
|
172
|
+
if ('tiers' in item) {
|
|
173
|
+
if (isOneshot) {
|
|
174
|
+
invalid.push({
|
|
175
|
+
path: `${wfPath}[].tiers`,
|
|
176
|
+
kind: 'invalid',
|
|
177
|
+
detail: 'oneshot_review_layers always runs regardless of tier — tiers has no effect here',
|
|
178
|
+
});
|
|
179
|
+
} else if (!Array.isArray(item.tiers)) {
|
|
180
|
+
invalid.push({
|
|
181
|
+
path: `${wfPath}[].tiers`,
|
|
182
|
+
kind: 'invalid',
|
|
183
|
+
detail: `expected an array of "small"/"normal"/"critical", got ${JSON.stringify(item.tiers)}`,
|
|
184
|
+
});
|
|
185
|
+
} else if (item.tiers.length === 0) {
|
|
186
|
+
invalid.push({
|
|
187
|
+
path: `${wfPath}[].tiers`,
|
|
188
|
+
kind: 'invalid',
|
|
189
|
+
detail: 'empty — the layer would never run on any tier',
|
|
190
|
+
});
|
|
191
|
+
} else {
|
|
192
|
+
for (const tier of item.tiers) {
|
|
193
|
+
if (!VALID_REVIEW_TIERS.has(tier)) {
|
|
194
|
+
invalid.push({
|
|
195
|
+
path: `${wfPath}[].tiers[]`,
|
|
196
|
+
kind: 'invalid',
|
|
197
|
+
detail: `invalid tier "${tier}" — expected one of "small", "normal", "critical"`,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if ('max_calls' in item && !(Number.isInteger(item.max_calls) && item.max_calls > 0)) {
|
|
204
|
+
invalid.push({
|
|
205
|
+
path: `${wfPath}[].max_calls`,
|
|
206
|
+
kind: 'invalid',
|
|
207
|
+
detail: `expected a positive integer, got ${JSON.stringify(item.max_calls)}`,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return invalid;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Walk a loaded TOML layer and collect unknown/invalid entries: unknown
|
|
215
|
+
* TOP-level tables/scalars (anything outside `workflow`, `agent`, `include`)
|
|
216
|
+
* plus the `[workflow]` fields and array-of-table item keys/values.
|
|
217
|
+
* `[agent]` is prose the LLM reads, not a code-consumed schema, so its keys
|
|
218
|
+
* are open by design; `include` is validated by resolveIncludes.
|
|
124
219
|
* @param {object} data - a single layer, as parsed (never a merged tree)
|
|
125
|
-
* @returns {string
|
|
220
|
+
* @returns {Array<{path: string, kind: 'unknown'|'invalid', detail?: string}>} caller prefixes with layer info
|
|
126
221
|
*/
|
|
127
222
|
function findUnknownKeys(data) {
|
|
128
223
|
const unknown = [];
|
|
129
224
|
if (!isPlainObject(data)) return unknown;
|
|
130
225
|
for (const [key, value] of Object.entries(data)) {
|
|
131
226
|
if (!TOP_LEVEL_KEYS.has(key)) {
|
|
132
|
-
unknown.push(key);
|
|
227
|
+
unknown.push({ path: key, kind: 'unknown' });
|
|
133
228
|
continue;
|
|
134
229
|
}
|
|
135
230
|
if (key === 'workflow' && isPlainObject(value)) unknown.push(...findUnknownWorkflowKeys(value));
|
|
@@ -140,9 +235,12 @@ function findUnknownKeys(data) {
|
|
|
140
235
|
module.exports = {
|
|
141
236
|
findUnknownKeys,
|
|
142
237
|
findUnknownWorkflowKeys,
|
|
238
|
+
findInvalidReviewLayerValues,
|
|
239
|
+
isArrayOfStrings,
|
|
143
240
|
WORKFLOW_KEYS,
|
|
144
241
|
PARTY_MEMBER_KEYS,
|
|
145
242
|
PARTY_GROUP_KEYS,
|
|
146
243
|
REVIEW_LAYER_KEYS,
|
|
244
|
+
VALID_REVIEW_TIERS,
|
|
147
245
|
TOP_LEVEL_KEYS,
|
|
148
246
|
};
|