iron-edge 2.2.0 → 2.3.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.
- package/README.md +14 -10
- package/agents/iron-3d.md +7 -1
- package/agents/iron-animations.md +14 -7
- package/agents/iron-auditor.md +1 -1
- package/agents/iron-brand.md +16 -7
- package/agents/iron-ceo.md +2 -1
- package/agents/iron-claude.md +16 -9
- package/agents/iron-clone.md +5 -2
- package/agents/iron-cloning.md +14 -7
- package/agents/iron-commercial.md +16 -7
- package/agents/iron-community.md +7 -1
- package/agents/iron-content.md +14 -7
- package/agents/iron-copy.md +16 -7
- package/agents/iron-council.md +16 -7
- package/agents/iron-courses.md +14 -7
- package/agents/iron-cyber.md +14 -7
- package/agents/iron-design.md +16 -7
- package/agents/iron-eng.md +1 -1
- package/agents/iron-finance.md +14 -7
- package/agents/iron-forge.md +1 -1
- package/agents/iron-growth.md +16 -7
- package/agents/iron-install.md +1 -1
- package/agents/iron-paidmedia.md +16 -7
- package/agents/iron-premium.md +1 -1
- package/agents/iron-product.md +16 -7
- package/agents/iron-research.md +14 -7
- package/agents/iron-search.md +1 -1
- package/agents/iron-shopify.md +2 -2
- package/agents/iron-spy.md +21 -9
- package/agents/iron-storytelling.md +16 -7
- package/agents/iron-video.md +7 -1
- package/agents/lp-cinematico.md +1 -1
- package/agents/xq-advisory.md +20 -3
- package/agents/xq-brand.md +19 -2
- package/agents/xq-clevel.md +14 -2
- package/agents/xq-codecore.md +25 -11
- package/agents/xq-copy.md +18 -2
- package/agents/xq-copymaster.md +108 -91
- package/agents/xq-cyber.md +26 -12
- package/agents/xq-data.md +16 -2
- package/agents/xq-design.md +24 -8
- package/agents/xq-hormozi.md +16 -2
- package/agents/xq-movement.md +14 -2
- package/agents/xq-storytelling.md +18 -2
- package/agents/xq-traffic.md +25 -8
- package/bin/iron-edge.js +75 -8
- package/installer/gerados.js +38 -2
- package/installer/install.js +21 -3
- package/installer/update.js +201 -20
- package/iron-routing-map.md +19 -2
- package/package.json +3 -2
- package/skills/blindar-sistema/SKILL.md +123 -0
- package/skills/blindar-sistema/advanced/frieren-opcional.md +61 -0
- package/skills/blindar-sistema/kb/idor.md +94 -0
- package/skills/blindar-sistema/kb/llm-injection.md +86 -0
- package/skills/blindar-sistema/kb/secrets.md +89 -0
- package/skills/blindar-sistema/kb/sqli.md +77 -0
- package/skills/blindar-sistema/procedures/llm-endpoint-hardening.md +80 -0
- package/skills/blindar-sistema/procedures/sandbox-dado-falso.md +63 -0
- package/skills/blindar-sistema/procedures/secret-hygiene.md +72 -0
- package/skills/gate/SKILL.md +6 -0
- package/skills/salvar/SKILL.md +21 -1
- package/squads/squad-brand/agents/al-ries.md +1 -2
- package/squads/squad-brand/agents/alina-wheeler.md +1 -2
- package/squads/squad-brand/agents/brand-orqx.md +2 -0
- package/squads/squad-brand/agents/byron-sharp.md +1 -2
- package/squads/squad-brand/agents/david-aaker.md +1 -2
- package/squads/squad-brand/agents/denise-yohn.md +1 -2
- package/squads/squad-brand/agents/donald-miller.md +1 -2
- package/squads/squad-brand/agents/emily-heyward.md +1 -2
- package/squads/squad-brand/agents/jean-noel-kapferer.md +1 -2
- package/squads/squad-brand/agents/kevin-keller.md +1 -2
- package/squads/squad-brand/agents/marty-neumeier.md +1 -2
- package/squads/squad-cloning/agents/mind-synthesizer.md +1 -0
- package/squads/squad-cloning/agents/source-hunter.md +1 -0
- package/squads/squad-commercial/agents/commercial-orqx.md +2 -0
- package/squads/squad-content/agents/signal-intelligence.md +1 -0
- package/squads/squad-copy/agents/ad-copywriter.md +1 -0
- package/squads/squad-copy/agents/andre-chaperon.md +1 -2
- package/squads/squad-copy/agents/ben-settle.md +1 -2
- package/squads/squad-copy/agents/claude-hopkins.md +1 -2
- package/squads/squad-copy/agents/clayton-makepeace.md +1 -2
- package/squads/squad-copy/agents/copy-orqx.md +2 -0
- package/squads/squad-copy/agents/dan-kennedy.md +1 -2
- package/squads/squad-copy/agents/dan-koe.md +1 -2
- package/squads/squad-copy/agents/david-deutsch.md +1 -2
- package/squads/squad-copy/agents/david-ogilvy.md +1 -2
- package/squads/squad-copy/agents/eugene-schwartz.md +1 -2
- package/squads/squad-copy/agents/frank-kern.md +1 -2
- package/squads/squad-copy/agents/funnel-copywriter.md +1 -0
- package/squads/squad-copy/agents/gary-bencivenga.md +1 -2
- package/squads/squad-copy/agents/gary-halbert.md +1 -2
- package/squads/squad-copy/agents/jim-rutz.md +1 -2
- package/squads/squad-copy/agents/joe-sugarman.md +1 -2
- package/squads/squad-copy/agents/john-carlton.md +1 -2
- package/squads/squad-copy/agents/jon-benson.md +1 -2
- package/squads/squad-copy/agents/parris-lampropoulos.md +1 -2
- package/squads/squad-copy/agents/persuasion-psychologist.md +1 -0
- package/squads/squad-copy/agents/robert-collier.md +1 -2
- package/squads/squad-copy/agents/russell-brunson.md +1 -2
- package/squads/squad-copy/agents/ry-schwartz.md +1 -2
- package/squads/squad-copy/agents/stefan-georgi.md +1 -2
- package/squads/squad-copy/agents/todd-brown.md +1 -2
- package/squads/squad-council/agents/brene-brown.md +1 -2
- package/squads/squad-council/agents/charlie-munger.md +1 -2
- package/squads/squad-council/agents/council-orqx.md +3 -1
- package/squads/squad-council/agents/derek-sivers.md +1 -2
- package/squads/squad-council/agents/naval-ravikant.md +1 -2
- package/squads/squad-council/agents/patrick-lencioni.md +1 -2
- package/squads/squad-council/agents/peter-thiel.md +1 -2
- package/squads/squad-council/agents/ray-dalio.md +1 -2
- package/squads/squad-council/agents/reid-hoffman.md +1 -2
- package/squads/squad-council/agents/simon-sinek.md +1 -2
- package/squads/squad-council/agents/yvon-chouinard.md +1 -2
- package/squads/squad-courses/agents/assessment-creator.md +1 -0
- package/squads/squad-cybersecurity/agents/cloud-security-engineer.md +1 -0
- package/squads/squad-cybersecurity/agents/compliance-officer.md +1 -0
- package/squads/squad-cybersecurity/agents/cyber-orqx.md +1 -0
- package/squads/squad-cybersecurity/agents/incident-responder.md +1 -0
- package/squads/squad-cybersecurity/agents/network-security-engineer.md +1 -0
- package/squads/squad-cybersecurity/agents/penetration-tester.md +1 -0
- package/squads/squad-cybersecurity/agents/soc-analyst.md +1 -0
- package/squads/squad-cybersecurity/agents/threat-analyst.md +1 -0
- package/squads/squad-design/agents/brad-frost.md +4 -3
- package/squads/squad-design/agents/dan-mall.md +4 -3
- package/squads/squad-design/agents/dave-malouf.md +4 -3
- package/squads/squad-design/agents/design-chief.md +2 -0
- package/squads/squad-design/agents/design-orqx.md +2 -0
- package/squads/squad-design/agents/nano-banana-generator.md +1 -0
- package/squads/squad-engineering/agents/eng-devops.md +1 -0
- package/squads/squad-engineering/agents/eng-product.md +1 -0
- package/squads/squad-engineering/agents/eng-sprint.md +1 -0
- package/squads/squad-finance/agents/budget-controller.md +1 -0
- package/squads/squad-growth/agents/growth-orqx.md +2 -0
- package/squads/squad-paidmedia/agents/depesh-mandalia.md +1 -2
- package/squads/squad-paidmedia/agents/kasim-aslam.md +1 -2
- package/squads/squad-paidmedia/agents/molly-pittman.md +1 -2
- package/squads/squad-paidmedia/agents/nicholas-kusmich.md +1 -2
- package/squads/squad-paidmedia/agents/paidmedia-orqx.md +2 -0
- package/squads/squad-paidmedia/agents/pedro-sobral.md +1 -2
- package/squads/squad-paidmedia/agents/ralph-burns.md +1 -2
- package/squads/squad-paidmedia/agents/tom-breeze.md +1 -2
- package/squads/squad-product/agents/product-orqx.md +2 -0
- package/squads/squad-research/agents/audience-intelligence.md +1 -0
- package/squads/squad-research/agents/competitive-intelligence.md +1 -0
- package/squads/squad-research/agents/trend-forecaster.md +1 -0
- package/squads/squad-storytelling/agents/blake-snyder.md +1 -2
- package/squads/squad-storytelling/agents/dan-harmon.md +1 -2
- package/squads/squad-storytelling/agents/joseph-campbell.md +1 -2
- package/squads/squad-storytelling/agents/keith-johnstone.md +1 -2
- package/squads/squad-storytelling/agents/kindra-hall.md +1 -2
- package/squads/squad-storytelling/agents/marshall-ganz.md +1 -2
- package/squads/squad-storytelling/agents/matthew-dicks.md +1 -2
- package/squads/squad-storytelling/agents/nancy-duarte.md +1 -2
- package/squads/squad-storytelling/agents/oren-klaff.md +1 -2
- package/squads/squad-storytelling/agents/park-howell.md +1 -2
- package/squads/squad-storytelling/agents/shawn-coyne.md +1 -2
- package/squads/squad-storytelling/agents/storytelling-orqx.md +2 -0
package/installer/update.js
CHANGED
|
@@ -31,7 +31,12 @@ const DRY = args.includes('--dry-run');
|
|
|
31
31
|
const srcIdx = args.indexOf('--source');
|
|
32
32
|
const SOURCE = srcIdx >= 0 && args[srcIdx + 1] ? path.resolve(args[srcIdx + 1]) : path.join(__dirname, '..');
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
// Escrita SINCRONA de proposito. Com `console.log` em Windows, quando a saida vai pra um cano
|
|
35
|
+
// (CI, `| tee`, terminal do Claude Code), o Node enfileira e o `process.exit()` do fim descarta o
|
|
36
|
+
// que ainda nao saiu: na CI de 22/09/2026 o log chegou comecando pelo MEIO, sem o cabecalho e sem
|
|
37
|
+
// o "Instalacao encontrada". Um aluno que cola o log pra pedir ajuda mandaria metade dele — e eu
|
|
38
|
+
// perdi duas rodadas de CI deduzindo em cima de um log truncado.
|
|
39
|
+
const log = (m) => { try { fs.writeSync(1, m + '\n'); } catch (_) { console.log(m); } };
|
|
35
40
|
function abort(msg) {
|
|
36
41
|
console.error('\n[ABORTADO] ' + msg);
|
|
37
42
|
process.exit(1);
|
|
@@ -50,7 +55,12 @@ function norm(p) {
|
|
|
50
55
|
let alvo = path.resolve(p);
|
|
51
56
|
let resto = '';
|
|
52
57
|
for (;;) {
|
|
53
|
-
|
|
58
|
+
// `realpathSync.native` (e nao o `realpathSync` puro) porque em Windows so' a versao nativa
|
|
59
|
+
// expande o nome curto 8.3: a mesma pasta aparece como C:\\Users\\RUNNER~1\\... e como
|
|
60
|
+
// C:\\Users\\runneradmin\\..., e comparar as duas como texto faz o update concluir que os 2129
|
|
61
|
+
// arquivos "nao existiam" — e sobrescrever o que a pessoa editou. Medido na CI de 22/09/2026:
|
|
62
|
+
// o manifesto guardava a forma longa e o calculo do momento devolvia a curta.
|
|
63
|
+
try { return path.join(fs.realpathSync.native(alvo), resto); }
|
|
54
64
|
catch (e) {
|
|
55
65
|
const pai = path.dirname(alvo);
|
|
56
66
|
if (pai === alvo) return path.resolve(p);
|
|
@@ -111,6 +121,17 @@ for (const f of man.files || []) {
|
|
|
111
121
|
if (dest) antigos.set(norm(dest), f);
|
|
112
122
|
}
|
|
113
123
|
log(`Instalacao encontrada: ${antigos.size} arquivos no manifesto`);
|
|
124
|
+
|
|
125
|
+
// Chave de diagnostico, ligada por variavel de ambiente. Existe porque em 22/09/2026 o update
|
|
126
|
+
// concluiu, numa maquina Windows, que 2129 arquivos "nao existiam" — com o manifesto certo, o
|
|
127
|
+
// hash certo e o caminho gravado igualzinho ao real. O que diverge e' a forma do caminho DEPOIS
|
|
128
|
+
// do `norm()`, e isso nao da pra ver de fora. Fica no produto de proposito: e' o que permite
|
|
129
|
+
// diagnosticar a maquina de um aluno sem chutar.
|
|
130
|
+
let dbgMostrado = false;
|
|
131
|
+
if (process.env.IRON_EDGE_DEBUG) {
|
|
132
|
+
const amostra = (m) => [...m.keys()].slice(0, 2).join('\n ');
|
|
133
|
+
log(' [debug] chaves do manifesto (antigos):\n ' + amostra(antigos));
|
|
134
|
+
}
|
|
114
135
|
if (man.label) log(` edicao: ${man.label}`);
|
|
115
136
|
if (man.installed_at) log(` instalada em: ${man.installed_at}`);
|
|
116
137
|
|
|
@@ -136,6 +157,7 @@ for (const dir of ['agents', 'skills', 'squads', 'docs']) {
|
|
|
136
157
|
const plano = { trocar: [], adicionar: [], restaurar: [], preservar: [], remover: [], manter: [], gerar: [] };
|
|
137
158
|
|
|
138
159
|
for (const [dest, origem] of novos) {
|
|
160
|
+
if (process.env.IRON_EDGE_DEBUG && !dbgMostrado) { dbgMostrado = true; log(' [debug] chave calculada agora (novos):\n ' + dest); }
|
|
139
161
|
const ent = antigos.get(dest);
|
|
140
162
|
if (!ent) { plano.adicionar.push({ dest, origem }); continue; }
|
|
141
163
|
if (ent.generated) { plano.manter.push({ dest, motivo: 'gerado na instalacao (config, manifesto)' }); continue; }
|
|
@@ -185,6 +207,59 @@ for (const g of GERADOS) {
|
|
|
185
207
|
}
|
|
186
208
|
}
|
|
187
209
|
|
|
210
|
+
// ---------------------------------------------------------------- config: so' relatorio
|
|
211
|
+
// O config NUNCA e' sobrescrito pelo update — e' da pessoa. Mas o que este bloco IMPRIME
|
|
212
|
+
// precisa sair sempre, e por isso ele e' funcao chamada antes do early-exit e nao codigo
|
|
213
|
+
// solto na FASE 3: la' embaixo ele so' rodava quando havia algo pra atualizar. Quem ja' esta'
|
|
214
|
+
// na versao do pacote — que e' justamente quem roda `update` so' pra conferir o cofre —
|
|
215
|
+
// batia no `process.exit(0)` 125 linhas antes e nunca via aviso nenhum. Um alerta contra
|
|
216
|
+
// falha silenciosa que falhava em silencio. Vale para os DOIS avisos daqui: o de campo
|
|
217
|
+
// faltando ja' nascia inalcancavel assim, e ninguem tinha percebido.
|
|
218
|
+
function relatarConfig() {
|
|
219
|
+
const cfg = path.join(IRON_DIR, 'config.yaml');
|
|
220
|
+
if (!fs.existsSync(cfg)) return;
|
|
221
|
+
// .replace de CRLF: o config.yaml e' editado pela PESSOA, e o Bloco de Notas do Windows grava
|
|
222
|
+
// com \r\n. Sem isto, o valor lido da linha vem com um \r invisivel grudado no fim — e o aviso
|
|
223
|
+
// de barra invertida passaria a julgar um caminho que ninguem digitou. Mesma classe do defeito
|
|
224
|
+
// que reprovou a CI de Windows em 22/09/2026.
|
|
225
|
+
const seu = fs.readFileSync(cfg, 'utf8').replace(/\r\n/g, '\n');
|
|
226
|
+
const campos = ['owner', 'businesses', 'vault_path'];
|
|
227
|
+
const faltando = campos.filter((c) => !new RegExp(`^\\s*${c}\\s*:`, 'm').test(seu));
|
|
228
|
+
log(' config.yaml: preservado como estava (nao foi tocado).');
|
|
229
|
+
if (faltando.length) {
|
|
230
|
+
log('');
|
|
231
|
+
log(' ATENCAO: a versao nova usa campos que o seu config nao tem:');
|
|
232
|
+
for (const f of faltando) log(` - ${f}`);
|
|
233
|
+
log(' Acrescente a mao, ou peca ao Iron: "atualiza meu config".');
|
|
234
|
+
if (faltando.includes('vault_path') && /obsidian_vault_path/.test(seu))
|
|
235
|
+
log(' (Voce tem "obsidian_vault_path": e o mesmo cofre. O Iron entende os dois nomes.)');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Barra invertida SOLTA dentro de aspas duplas. O par `\\` e' escape valido e correto — e'
|
|
239
|
+
// o formato que instalacoes antigas gravavam —, entao ele e' consumido ANTES da checagem:
|
|
240
|
+
// avisar nele mandaria a pessoa "consertar" um arquivo que esta' certo. O que sobra e'
|
|
241
|
+
// barra solta, e ela tem dois destinos, os dois ruins: `\U` de C:\Users\<voce> nao e escape
|
|
242
|
+
// valido e derruba o arquivo INTEIRO (o owner morre junto), enquanto `\b` de C:\backup e'
|
|
243
|
+
// escape valido, parseia sem erro nenhum e entrega um caminho CORROMPIDO em silencio.
|
|
244
|
+
// Isola o valor ENTRE ASPAS. Usar a linha inteira fazia comentario a direita entrar na conta:
|
|
245
|
+
// `vault_path: "C:/Users/<voce>/Cofre" # nota com contra\barra` disparava o aviso com o
|
|
246
|
+
// caminho intacto. Aspas duplas nao podem existir dentro de nome de arquivo no Windows, entao
|
|
247
|
+
// [^"]* fecha no lugar certo.
|
|
248
|
+
const linha = (seu.match(/^\s*(?:obsidian_)?vault_path\s*:\s*(.+)$/m) || [])[1];
|
|
249
|
+
const aspas = linha ? linha.trim().match(/^"([^"]*)"/) : null;
|
|
250
|
+
const valor = aspas ? aspas[1] : '';
|
|
251
|
+
if (valor && valor.split('\\\\').join('').includes('\\')) {
|
|
252
|
+
log('');
|
|
253
|
+
log(' ATENCAO: seu vault_path tem barra invertida dentro de aspas duplas:');
|
|
254
|
+
log(' ' + aspas[0]);
|
|
255
|
+
log(' Em YAML isso ou invalida o arquivo INTEIRO (e o owner morre junto), ou entrega um');
|
|
256
|
+
log(' caminho corrompido sem dar erro nenhum. Nos dois casos o sintoma e o mesmo: o Iron');
|
|
257
|
+
log(' passa a responder generico e a oferecer criar um cofre que voce ja tem.');
|
|
258
|
+
log(' Troque as barras invertidas por barras normais (C:/Users/<voce>/...) e salve.');
|
|
259
|
+
log(' Ou peca ao Iron: "conserta meu vault_path".');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
188
263
|
// ---------------------------------------------------------------- mostrar
|
|
189
264
|
log('');
|
|
190
265
|
log('--- PLANO ---');
|
|
@@ -213,8 +288,102 @@ log('');
|
|
|
213
288
|
log(' Seu config.yaml, seus agentes e skills proprios, e seu cofre NAO entram neste plano.');
|
|
214
289
|
log(' Config e manifesto sao regravados no fim; o resto nem e olhado.');
|
|
215
290
|
|
|
291
|
+
log('');
|
|
292
|
+
relatarConfig();
|
|
293
|
+
|
|
294
|
+
|
|
216
295
|
if (DRY) { log(''); log('--dry-run: nada foi escrito.'); process.exit(0); }
|
|
217
|
-
|
|
296
|
+
|
|
297
|
+
// Permissao da pasta de segredos, TAMBEM no update. O chmod entrou no install no ciclo 2 e o
|
|
298
|
+
// gate mediu a lacuna: quem ja tinha o Iron instalado seguia com `secrets/` em 755, porque o
|
|
299
|
+
// caminho de manutencao e' o update, nao reinstalar. Roda DUAS vezes de proposito: aqui, antes
|
|
300
|
+
// do early-exit (senao nao alcanca quem nao tem nada a atualizar) e de novo no fim, porque a
|
|
301
|
+
// FASE 2 pode RECRIAR a pasta com o umask padrao — medido no gate, rodada 4.
|
|
302
|
+
// Idempotente e falha aberto fora de POSIX.
|
|
303
|
+
function fecharPermissoes() {
|
|
304
|
+
for (const [alvo, modo] of [[path.join(IRON_DIR, 'secrets'), 0o700], [path.join(IRON_DIR, 'integrations', 'shopify.yaml'), 0o600]]) {
|
|
305
|
+
try {
|
|
306
|
+
if (!fs.existsSync(alvo)) continue;
|
|
307
|
+
const atual = fs.statSync(alvo).mode & 0o777;
|
|
308
|
+
if (atual === modo) continue;
|
|
309
|
+
fs.chmodSync(alvo, modo);
|
|
310
|
+
log(` permissao de ${path.basename(alvo)} fechada (${atual.toString(8)} -> ${modo.toString(8)})`);
|
|
311
|
+
} catch (_) { /* Windows e sistemas sem POSIX: a ACL do perfil ja cobre */ }
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
fecharPermissoes();
|
|
315
|
+
|
|
316
|
+
// metadata.json ilegivel tambem se conserta ANTES do early-exit: o aluno que ja esta na versao do
|
|
317
|
+
// pacote e roda update justamente pra consertar caia em "Nada a fazer" e ficava travado, com o
|
|
318
|
+
// verify reprovando para sempre. Mesmo padrao do chmod, mesmo erro, medido na rodada 4.
|
|
319
|
+
// `somenteSeQuebrado` existe porque a chamada de antes do early-exit nao pode reescrever um
|
|
320
|
+
// metadata SADIO: mudar o arquivo sem regravar o manifesto (que so acontece na FASE 3) deixaria
|
|
321
|
+
// disco e registro divergentes, e o verify reprovaria — o mesmo defeito da rodada 2, por outra porta.
|
|
322
|
+
// Devolve true quando ESCREVEU, e quem chamou usa isso pra nao sair pelo early-exit.
|
|
323
|
+
function repararMetadata(versao, somenteSeQuebrado) {
|
|
324
|
+
const META = path.join(IRON_DIR, 'metadata.json');
|
|
325
|
+
// Arquivo AUSENTE nao e' arquivo quebrado: instalacao antiga simplesmente nao tinha metadata, e
|
|
326
|
+
// inventar um na chamada de ANTES do early-exit transformaria um update no-op em escrita (isso
|
|
327
|
+
// quebrou 8 casos do proprio test-update). Mas na FASE 3, com versao pra gravar, ausente TEM que
|
|
328
|
+
// ser recriado — a rodada 5 mediu que a guarda simples apagava a entrada do manifesto em silencio.
|
|
329
|
+
if (!fs.existsSync(META)) {
|
|
330
|
+
// Sumido MAS registrado no manifesto = o produto sabe que ele deveria existir; entao recriar
|
|
331
|
+
// e' conserto, nao invencao, e vale tambem antes do early-exit: sem isso o verify reprovava
|
|
332
|
+
// com "ausentes: 1" e o update respondia "nada a fazer" — remedio que o proprio produto
|
|
333
|
+
// receita e nao cura, o mesmo padrao do shopify.yaml na rodada 1.
|
|
334
|
+
const registrado = [...antigos.keys()].some((d) => d.endsWith('metadata.json'));
|
|
335
|
+
if (somenteSeQuebrado && !registrado) return false;
|
|
336
|
+
if (!versao) {
|
|
337
|
+
const doMan = (man.version || '').replace(/^iron-edge-/, '');
|
|
338
|
+
if (!doMan) return false;
|
|
339
|
+
versao = doMan; // recria com a versao que o manifesto registra
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
fs.writeFileSync(META, JSON.stringify({ version: 'iron-edge-' + versao, label: 'Iron Edge v' + versao, updated_at: new Date().toISOString(), recriado_por_estar_ausente: true }, null, 2));
|
|
343
|
+
log(' metadata.json estava ausente: recriado');
|
|
344
|
+
return true;
|
|
345
|
+
} catch (e) { log(` aviso: nao consegui criar metadata.json (${e.message})`); return false; }
|
|
346
|
+
}
|
|
347
|
+
let meta = null, precisaReescrever = false, ilegivel = false;
|
|
348
|
+
try { meta = JSON.parse(fs.readFileSync(META, 'utf8')); }
|
|
349
|
+
catch (_) { precisaReescrever = true; ilegivel = true; }
|
|
350
|
+
// JSON valido que NAO e' objeto plano (array, numero, texto, null) engole a atribuicao sem
|
|
351
|
+
// erro e o arquivo fica com lixo enquanto o log diz "atualizado" — medido na rodada 4.
|
|
352
|
+
if (!precisaReescrever && (meta === null || typeof meta !== 'object' || Array.isArray(meta))) { precisaReescrever = true; ilegivel = true; }
|
|
353
|
+
// Conteudo que NAO bate com o hash registrado tambem e' reparo: o metadata.json e' derivado,
|
|
354
|
+
// ninguem deveria edita-lo, e quando alguem edita o verify reprova para sempre sem caminho de
|
|
355
|
+
// volta (medido na matriz de estados: "metadata com campo extra do aluno").
|
|
356
|
+
if (!precisaReescrever) {
|
|
357
|
+
const ent = [...antigos.values()].find((e) => String(e.dest).endsWith('metadata.json'));
|
|
358
|
+
if (ent && ent.sha256 && ent.sha256 !== sha256(META)) precisaReescrever = true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (!precisaReescrever && (somenteSeQuebrado || !versao)) return false;
|
|
362
|
+
// Reparo por DIVERGENCIA (o arquivo esta legivel, so' nao bate com o registro) preserva o que
|
|
363
|
+
// estiver la: quem escreveu algo dentro nao pode perder isso porque o hash saiu de sincronia.
|
|
364
|
+
// Reescrita do zero fica so' para o arquivo ilegivel, onde nao ha o que preservar.
|
|
365
|
+
const base = ilegivel ? { recuperado_de_arquivo_ilegivel: true } : (meta || {});
|
|
366
|
+
if (versao) { base.version = 'iron-edge-' + versao; base.label = 'Iron Edge v' + versao; }
|
|
367
|
+
base.updated_at = new Date().toISOString();
|
|
368
|
+
try {
|
|
369
|
+
fs.writeFileSync(META, JSON.stringify(base, null, 2));
|
|
370
|
+
log(ilegivel ? ' metadata.json estava ilegivel: reescrito do zero'
|
|
371
|
+
: precisaReescrever ? ` metadata.json estava fora de sincronia: reconciliado (${base.version})`
|
|
372
|
+
: ` metadata.json atualizado para ${base.version}`);
|
|
373
|
+
return true;
|
|
374
|
+
} catch (e) { log(` aviso: nao consegui gravar metadata.json (${e.message})`); return false; }
|
|
375
|
+
}
|
|
376
|
+
// so conserta o que esta quebrado; se consertou, o update NAO pode sair pelo early-exit, porque
|
|
377
|
+
// o manifesto precisa da FASE 3 pra registrar o hash novo.
|
|
378
|
+
const metadataReparado = repararMetadata(process.env.IRON_EDGE_VERSION || '', true);
|
|
379
|
+
|
|
380
|
+
// "Nada a fazer" so' vale se a VERSAO tambem ja for a deste pacote. Release que nao mexe em
|
|
381
|
+
// nenhum arquivo rastreado (so' installer/, scripts/, README) caia aqui e a versao registrada
|
|
382
|
+
// ficava velha para sempre — e a mensagem dizia justamente que ja estava na versao do pacote.
|
|
383
|
+
const versaoRegistrada = man.version || '';
|
|
384
|
+
const versaoDoPacote = process.env.IRON_EDGE_VERSION ? 'iron-edge-' + process.env.IRON_EDGE_VERSION : '';
|
|
385
|
+
const versaoDesatualizada = Boolean(versaoDoPacote) && versaoRegistrada !== versaoDoPacote;
|
|
386
|
+
if (!plano.trocar.length && !plano.adicionar.length && !plano.restaurar.length && !plano.remover.length && !plano.gerar.length && !plano.preservar.length && !versaoDesatualizada && !metadataReparado) {
|
|
218
387
|
log(''); log('Nada a fazer: sua instalacao ja esta na versao deste pacote.');
|
|
219
388
|
process.exit(0);
|
|
220
389
|
}
|
|
@@ -274,6 +443,26 @@ let geradosOk = 0;
|
|
|
274
443
|
for (const g of plano.gerar) {
|
|
275
444
|
try {
|
|
276
445
|
fs.mkdirSync(path.dirname(g.dest), { recursive: true });
|
|
446
|
+
// Achado tardio da rodada 4: `~/.claude/commands/` e' pasta generica do Claude Code, e um
|
|
447
|
+
// arquivo com o mesmo nome que NAO esteja no manifesto era sobrescrito sem aviso e sem
|
|
448
|
+
// backup — perda de dado silenciosa do que era do usuario. Agora vira .iron-old antes.
|
|
449
|
+
if (fs.existsSync(g.dest)) {
|
|
450
|
+
const registrado = antigos.has(norm(g.dest));
|
|
451
|
+
if (!registrado) {
|
|
452
|
+
// .iron-old ja existente pode ser backup ANTERIOR do usuario, ou sobra de execucao
|
|
453
|
+
// interrompida: sobrescrever aqui seria repetir, dentro do mecanismo de backup, a perda
|
|
454
|
+
// de dado que ele foi criado pra evitar (medido na rodada 5). Entao abre um nome novo.
|
|
455
|
+
let backup = g.dest + '.iron-old';
|
|
456
|
+
if (fs.existsSync(backup)) {
|
|
457
|
+
const carimbo = new Date().toISOString().replace(/[:.]/g, '-');
|
|
458
|
+
backup = `${g.dest}.iron-old-${carimbo}`;
|
|
459
|
+
}
|
|
460
|
+
try {
|
|
461
|
+
fs.copyFileSync(g.dest, backup);
|
|
462
|
+
log(` ${path.basename(g.dest)} existia e nao era do pacote: copia guardada em ${path.basename(backup)}`);
|
|
463
|
+
} catch (e2) { log(` aviso: nao consegui guardar copia de ${g.dest} (${e2.message}) — nao sobrescrevi`); continue; }
|
|
464
|
+
}
|
|
465
|
+
}
|
|
277
466
|
fs.writeFileSync(g.dest, g.conteudo);
|
|
278
467
|
geradosOk++;
|
|
279
468
|
} catch (e) { log(` aviso: nao consegui gravar ${g.dest} (${e.code || e.message})`); }
|
|
@@ -284,6 +473,14 @@ if (geradosOk) log(` ${geradosOk} arquivo(s) gerado(s) desta versao criados (ex
|
|
|
284
473
|
log('');
|
|
285
474
|
log('--- FASE 3: manifesto e config ---');
|
|
286
475
|
|
|
476
|
+
|
|
477
|
+
// A VERSAO PRIMEIRO, o manifesto DEPOIS. Na rodada 2 do gate o metadata.json era reescrito no
|
|
478
|
+
// fim, depois de o manifesto ja ter guardado o hash do conteudo ANTIGO: disco e registro
|
|
479
|
+
// discordavam e o verify passava a reprovar uma instalacao sadia para sempre.
|
|
480
|
+
const versaoNova = process.env.IRON_EDGE_VERSION || '';
|
|
481
|
+
repararMetadata(versaoNova);
|
|
482
|
+
fecharPermissoes(); // a FASE 2 pode ter recriado secrets/ com o umask padrao
|
|
483
|
+
|
|
287
484
|
const arquivos = [];
|
|
288
485
|
const editados = new Set(plano.preservar.map((x) => norm(x.dest)));
|
|
289
486
|
for (const [dest, origem] of novos) {
|
|
@@ -310,6 +507,7 @@ for (const g of GERADOS) {
|
|
|
310
507
|
|
|
311
508
|
const novoMan = {
|
|
312
509
|
...man,
|
|
510
|
+
...(versaoNova ? { version: 'iron-edge-' + versaoNova, label: 'Iron Edge v' + versaoNova } : {}),
|
|
313
511
|
updated_at: new Date().toISOString(),
|
|
314
512
|
previous_installed_at: man.installed_at,
|
|
315
513
|
files: arquivos,
|
|
@@ -317,23 +515,6 @@ const novoMan = {
|
|
|
317
515
|
fs.writeFileSync(MANIFEST, JSON.stringify(novoMan, null, 2));
|
|
318
516
|
log(` manifesto regravado: ${arquivos.length} arquivos`);
|
|
319
517
|
|
|
320
|
-
// config: NUNCA sobrescrito. Mas se a versao nova tem campo que o seu nao tem, avisa.
|
|
321
|
-
const cfg = path.join(IRON_DIR, 'config.yaml');
|
|
322
|
-
if (fs.existsSync(cfg)) {
|
|
323
|
-
const seu = fs.readFileSync(cfg, 'utf8');
|
|
324
|
-
const campos = ['owner', 'businesses', 'vault_path'];
|
|
325
|
-
const faltando = campos.filter((c) => !new RegExp(`^\\s*${c}\\s*:`, 'm').test(seu));
|
|
326
|
-
log(' config.yaml: preservado como estava (nao foi tocado).');
|
|
327
|
-
if (faltando.length) {
|
|
328
|
-
log('');
|
|
329
|
-
log(' ATENCAO: a versao nova usa campos que o seu config nao tem:');
|
|
330
|
-
for (const f of faltando) log(` - ${f}`);
|
|
331
|
-
log(' Acrescente a mao, ou peca ao Iron: "atualiza meu config".');
|
|
332
|
-
if (faltando.includes('vault_path') && /obsidian_vault_path/.test(seu))
|
|
333
|
-
log(' (Voce tem "obsidian_vault_path": e o mesmo cofre. O Iron entende os dois nomes.)');
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
518
|
log('');
|
|
338
519
|
log('================================');
|
|
339
520
|
log(' Atualizado com sucesso');
|
package/iron-routing-map.md
CHANGED
|
@@ -63,10 +63,14 @@ o iron-ceo oferece: iron-X (rapido) ou xq-X (profundo)? [r/p]
|
|
|
63
63
|
| iron-forge | Construtor de agentes | criar agente, criar skill, novo agente, construir ferramenta |
|
|
64
64
|
| iron-install | Instalacao | instalar, setup do ecossistema, configurar ambiente |
|
|
65
65
|
| iron-claude | Claude Code em si | hook, MCP, skill, subagente, settings.json, contexto, memoria, "o Claude esta estranho", auditoria da instalacao |
|
|
66
|
-
| iron-clone | Clonar SITE + design system | clonar site, copiar pagina, espelhar site,
|
|
66
|
+
| iron-clone | Clonar SITE + design system | clonar site, copiar pagina, espelhar site, DNA visual de referencia, extrair paleta, transplante de marca, organizar HTML |
|
|
67
67
|
| iron-eng | Engenharia de software | app, plataforma, sistema, API, backend, banco, login, painel, dashboard, CRM, integracao, automacao de negocio, script, job, bug, erro, quebrou, deploy, migration |
|
|
68
68
|
| lp-cinematico | Landing pages scroll | landing page, LP cinematografica, scroll-scrub, pagina de captura |
|
|
69
69
|
|
|
70
|
+
> **iron-clone x iron-cloning:** o `iron-clone` copia SITE e identidade visual; o `iron-cloning` copia o MODO DE PENSAR
|
|
71
|
+
> de uma pessoa real (e exige consentimento e base legal). A skill `clone-site` nao roda nativamente em Windows, e o
|
|
72
|
+
> proprio `iron-clone` avisa isso antes de comecar.
|
|
73
|
+
|
|
70
74
|
## 3. Routing Table — Camada de Aprofundamento (XQ — 13)
|
|
71
75
|
|
|
72
76
|
| Agente | Sobe da camada | Quando usar |
|
|
@@ -77,7 +81,7 @@ o iron-ceo oferece: iron-X (rapido) ou xq-X (profundo)? [r/p]
|
|
|
77
81
|
| xq-codecore | squad-claude | maestria em Claude Code |
|
|
78
82
|
| xq-copy | iron-copy | copy com mestres (Halbert, Schwartz, Ogilvy) |
|
|
79
83
|
| xq-copymaster | iron-copy | copy expandida — 33 copywriters |
|
|
80
|
-
| xq-cyber | iron-cyber | seguranca
|
|
84
|
+
| xq-cyber | iron-cyber | seguranca em profundidade (7 especialistas instalados) |
|
|
81
85
|
| xq-data | iron-growth | analytics e metricas em profundidade |
|
|
82
86
|
| xq-design | iron-design | UX/UI com experts |
|
|
83
87
|
| xq-hormozi | iron-commercial | ofertas estilo Grand Slam Offers |
|
|
@@ -274,3 +278,16 @@ pacote e nao entram na Routing Table.
|
|
|
274
278
|
Regra: o iron-ceo NAO aciona agente que nao esteja na Routing Table num
|
|
275
279
|
fluxo normal — so quando voce pedir pelo nome. Isso evita que um agente de
|
|
276
280
|
projeto especifico seja escolhido para uma demanda generica.
|
|
281
|
+
|
|
282
|
+
## 8. Especialistas com nome de pessoa real
|
|
283
|
+
|
|
284
|
+
Arquivos de especialista marcados com `real_person: true` levam o nome de uma pessoa que existe. A regra, sem exceção, para qualquer agente que assuma um deles:
|
|
285
|
+
|
|
286
|
+
1. O agente aplica o MÉTODO público da pessoa (frameworks, princípios, obras publicadas). Ele não é a pessoa e não diz que é. Forma correta: "no método de Gary Halbert". Forma errada: "eu sou Gary Halbert".
|
|
287
|
+
2. Nunca atribuir à pessoa frase, opinião, endosso ou fato que não esteja em obra publicada dela. O que for dedução do modelo se declara como dedução: "pelo método dele, eu diria que...".
|
|
288
|
+
3. O entregável nunca sai assinado com o nome da pessoa, nem sugere que ela revisou, aprovou ou participou.
|
|
289
|
+
4. Se o pedido for conteúdo que finja ser da pessoa (post "escrito por", depoimento, citação inventada), o agente recusa essa parte e entrega a versão "no método de".
|
|
290
|
+
|
|
291
|
+
5. **Pessoa falecida** (o arquivo registra a morte, seja num campo `died:` seja dentro do `born:`, como "1926 (died 2022)") exige o cuidado maior, não o menor: ela não pode desmentir nada, e o espólio costuma manter direito de imagem. Nesses casos, cite a obra publicada de forma literal e atribuída, e nunca produza frase nova no nome dela.
|
|
292
|
+
|
|
293
|
+
Esta seção é a fonte única desta regra. Quem dá entrada nesses especialistas aponta pra cá; não existe cópia da regra nos arquivos de especialista.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iron-edge",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Ecossistema de agentes para Claude Code: 44 agentes de topo, 18 squads e 14 skills, com quality gate, memoria e instalador que nunca sobrescreve.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"node": ">=18.0.0"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"test": "node scripts/lint.js"
|
|
34
|
+
"test": "node scripts/lint.js && node scripts/test-verify.js && node scripts/test-update.js",
|
|
35
|
+
"test:matriz": "node scripts/test-matriz-update.js"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"js-yaml": "^5.3.0"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: blindar-sistema
|
|
3
|
+
description: >
|
|
4
|
+
Auditoria de segurança guiada para quem programa com IA (vibe coding) e não
|
|
5
|
+
domina segurança. O Iron LÊ o código do próprio sistema do usuário (white-box):
|
|
6
|
+
acha as portas abertas mais comuns sem proxy, sem certificado, sem instalar nada,
|
|
7
|
+
sem mandar dado para fora, e roda em Mac e Windows. Traduz cada achado em
|
|
8
|
+
linguagem simples com prioridade e conduz o conserto. Use quando o usuário disser
|
|
9
|
+
"blindar meu sistema", "auditar a segurança do meu app/site", "revisar segurança",
|
|
10
|
+
"meu sistema está seguro?", ou pedir revisão de um sistema feito com IA.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# blindar-sistema
|
|
14
|
+
|
|
15
|
+
Você é um copiloto de segurança para quem programa com IA e não entende de
|
|
16
|
+
segurança. Fale simples, sem jargão. Explique cada termo técnico na hora, em uma
|
|
17
|
+
linha. Nunca assuste sem dar o conserto. Uma coisa de cada vez.
|
|
18
|
+
|
|
19
|
+
Você audita LENDO o código do sistema do próprio usuário. Você não ataca nada, não
|
|
20
|
+
liga proxy, não instala ferramenta, não manda dado para fora. Todo achado sai da
|
|
21
|
+
leitura do repositório.
|
|
22
|
+
|
|
23
|
+
## Regra dura
|
|
24
|
+
|
|
25
|
+
Só audite um sistema que é do próprio usuário. Você só LÊ o código, isso é seguro
|
|
26
|
+
em qualquer ambiente. Se em algum momento o usuário quiser o teste ao vivo (a etapa
|
|
27
|
+
avançada, com a ferramenta externa Frieren), aí vale a trava: só contra sistema
|
|
28
|
+
dele, só em ambiente de teste com dado sintético, nunca produção, nunca dado de
|
|
29
|
+
gente real, nunca site de terceiro. Veja `advanced/frieren-opcional.md`.
|
|
30
|
+
|
|
31
|
+
## A base de conhecimento
|
|
32
|
+
|
|
33
|
+
Cada família de falha tem um arquivo em `kb/` que diz onde procurar no código, o
|
|
34
|
+
padrão vulnerável vs. o seguro, como confirmar e o conserto. LEIA o arquivo da
|
|
35
|
+
família antes de auditar aquela família, não audite de memória.
|
|
36
|
+
|
|
37
|
+
- `kb/idor.md`: um cliente vê o dado de outro
|
|
38
|
+
- `kb/secrets.md`: chave/segredo exposto (front, histórico do Git, resposta da API)
|
|
39
|
+
- `kb/sqli.md`: injeção de SQL
|
|
40
|
+
- `kb/llm-injection.md`: injeção de prompt no endpoint de IA
|
|
41
|
+
|
|
42
|
+
Procedimentos de conserto que o usuário faz junto:
|
|
43
|
+
- `procedures/secret-hygiene.md`: revogar, rotacionar e limpar segredo do Git
|
|
44
|
+
- `procedures/llm-endpoint-hardening.md`: blindar o próprio LLM do sistema
|
|
45
|
+
- `procedures/sandbox-dado-falso.md`: montar um ambiente de teste com dado
|
|
46
|
+
sintético ANTES de qualquer prova viva ou da etapa avançada (Frieren)
|
|
47
|
+
|
|
48
|
+
## Fluxo
|
|
49
|
+
|
|
50
|
+
### Fase 0: Medir o sistema (antes de qualquer coisa)
|
|
51
|
+
|
|
52
|
+
NÃO assuma arquivo, rota ou botão que não viu. Meça primeiro. Pergunte uma de cada
|
|
53
|
+
vez o que não der para descobrir lendo:
|
|
54
|
+
|
|
55
|
+
1. Onde fica o código do sistema (a pasta do repositório)?
|
|
56
|
+
2. Que stack é (linguagem/framework)? Descubra lendo: `package.json`, arquivos de
|
|
57
|
+
config, estrutura de pastas. Só pergunte se não der para ler.
|
|
58
|
+
3. O sistema usa IA/LLM por trás (chatbot, geração de texto, agente)?
|
|
59
|
+
4. Usa banco? Qual (Supabase, Postgres, Mongo)?
|
|
60
|
+
|
|
61
|
+
Depois, MEÇA o terreno lendo o repo, e anote para si:
|
|
62
|
+
- Onde estão os handlers de rota (onde o servidor responde pedidos).
|
|
63
|
+
- Onde está a autenticação (login, sessão, middleware).
|
|
64
|
+
- Se usa Supabase: quais tabelas existem e quais têm RLS ligada.
|
|
65
|
+
- Onde texto externo entra no prompt do modelo (se usa IA).
|
|
66
|
+
- Onde poderia haver segredo (env, config, histórico do Git).
|
|
67
|
+
|
|
68
|
+
Se não houver como ler o repo (o usuário não apontou a pasta), pare e peça a pasta.
|
|
69
|
+
Não invente a estrutura.
|
|
70
|
+
|
|
71
|
+
### Fase 1: Auditar família por família, LENDO o código
|
|
72
|
+
|
|
73
|
+
Para cada família, abra o `kb/` dela e siga o "onde procurar" e o "como confirmar".
|
|
74
|
+
Ordem sugerida (as portas mais comuns primeiro): IDOR, segredos, SQL, injeção de
|
|
75
|
+
prompt (se usa IA). Depois, se der tempo, as demais famílias conforme forem adicionadas.
|
|
76
|
+
|
|
77
|
+
Para cada achado, registre: arquivo e linha, o que é (uma frase de leigo), o risco
|
|
78
|
+
concreto para o negócio dele, e o conserto em passos curtos no framework dele.
|
|
79
|
+
|
|
80
|
+
### Fase 2: Traduzir e priorizar
|
|
81
|
+
|
|
82
|
+
Não despeje a lista inteira. Ordene por gravidade e comece pelos 3 mais graves.
|
|
83
|
+
|
|
84
|
+
Crítico é qualquer coisa que exponha dado de cliente, segredo, ou dê acesso sem
|
|
85
|
+
login. Para cada achado dos 3 primeiros:
|
|
86
|
+
- Uma frase do que é, em linguagem de leigo.
|
|
87
|
+
- O risco concreto (o que um estranho consegue fazer).
|
|
88
|
+
- O conserto em passos curtos, com exemplo no framework dele.
|
|
89
|
+
|
|
90
|
+
### Fase 3: Plano curto
|
|
91
|
+
|
|
92
|
+
Monte: o que consertar hoje (crítico), esta semana, depois. Uma linha cada.
|
|
93
|
+
|
|
94
|
+
### Fase 4: As correções que exigem procedimento
|
|
95
|
+
|
|
96
|
+
Quando aparecer segredo no Git ou endpoint de IA sem proteção, conduza pelo
|
|
97
|
+
`procedures/`. São as duas correções que pedem passo a passo (revogar/limpar
|
|
98
|
+
segredo; blindar o LLM). Uma etapa de cada vez, esperando o usuário terminar.
|
|
99
|
+
|
|
100
|
+
### Fase 5: Fechar
|
|
101
|
+
|
|
102
|
+
Entregue um checklist do que foi corrigido e do que ficou de tarefa. Lembre que,
|
|
103
|
+
depois de consertar, vale reauditar: a correção às vezes cria o defeito seguinte.
|
|
104
|
+
|
|
105
|
+
## Se der errado
|
|
106
|
+
|
|
107
|
+
- **Não acho o código / a pasta está vazia:** peça o caminho certo do repositório.
|
|
108
|
+
Não audite pela tela pública; sem o código, a auditoria é chute.
|
|
109
|
+
- **O repo é enorme e trava a leitura:** comece pelos handlers de rota e pela pasta
|
|
110
|
+
de API; é onde as portas abertas moram. Não precisa ler tudo de uma vez.
|
|
111
|
+
- **Não tenho certeza se um achado é real:** trate como hipótese, marque "a
|
|
112
|
+
confirmar" e diga ao usuário como provar no sandbox de teste. Nunca afirme falha
|
|
113
|
+
sem a linha do código que a prova.
|
|
114
|
+
- **Windows:** os comandos de varredura dos `kb/` usam `git grep`, que roda igual no
|
|
115
|
+
Mac e no Windows (é o próprio Git). Onde um `kb/` traz um pipe `git log -p | grep`,
|
|
116
|
+
ele dá também a versão PowerShell com `Select-String`. Nada de encadear com `&&`
|
|
117
|
+
(o PowerShell 5.1 não aceita); uma linha por comando.
|
|
118
|
+
|
|
119
|
+
## Tom
|
|
120
|
+
|
|
121
|
+
Vibe coder não tem culpa de não saber isto. Trate como quem constrói rápido e
|
|
122
|
+
precisa de um segundo par de olhos, não como quem errou. Elogie o que já está certo.
|
|
123
|
+
Sempre técnico mais tradução fácil, lado a lado. Uma coisa de cada vez.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Etapa avançada (opcional): teste ao vivo com o Frieren
|
|
2
|
+
|
|
3
|
+
> Parte do pacote **blindar-sistema**. Isto é OPCIONAL e AVANÇADO. A trilha
|
|
4
|
+
> principal (a skill lendo o código) NÃO depende disto. Só siga aqui se você quer o
|
|
5
|
+
> teste ao vivo e já montou o sandbox de dado falso (`procedures/sandbox-dado-falso.md`).
|
|
6
|
+
|
|
7
|
+
## Por que existe
|
|
8
|
+
|
|
9
|
+
A auditoria lendo o código pega quase tudo, e sem risco. Mas algumas falhas só
|
|
10
|
+
aparecem com o sistema RODANDO: injeção cega por tempo, comportamento de cache,
|
|
11
|
+
fluxo de várias etapas (carrinho, cupom, checkout), upload de arquivo. Para esses
|
|
12
|
+
casos existe uma ferramenta externa aberta, o **Frieren DAST-AI** (KnowBe4, licença
|
|
13
|
+
Apache 2.0), que testa o sistema por dentro como um invasor faria.
|
|
14
|
+
|
|
15
|
+
Ela cobre o que a leitura de código não vê, ao custo de 4 riscos que você precisa
|
|
16
|
+
conhecer ANTES de ligar.
|
|
17
|
+
|
|
18
|
+
## Os 4 riscos (nomeados, para você decidir com os olhos abertos)
|
|
19
|
+
|
|
20
|
+
1. **O comando de instalação roda código de terceiro.** O `make setup` executa um
|
|
21
|
+
arquivo que vem do download. Rode só numa máquina onde isso é aceitável.
|
|
22
|
+
2. **Instalar o certificado do proxy é instalar uma autoridade.** O Frieren fica no
|
|
23
|
+
meio e descriptografa o tráfego seguro. Enquanto o certificado estiver confiado
|
|
24
|
+
no navegador, TODO o seu tráfego pode ser lido, não só o sistema em teste. Use um
|
|
25
|
+
navegador ou perfil separado, só para o teste, e remova o certificado no fim.
|
|
26
|
+
3. **O tráfego capturado vai para uma LLM.** Os agentes mandam o que capturaram para
|
|
27
|
+
o provedor de IA, na sua chave. Rodar contra dado real é dado saindo da máquina.
|
|
28
|
+
Por isso: SÓ contra o sandbox de dado falso.
|
|
29
|
+
4. **A ferramenta é nova.** Projeto recente, pouca revisão da comunidade. A marca por
|
|
30
|
+
trás é real; o código ainda é jovem. Trate como ferramenta de laboratório.
|
|
31
|
+
|
|
32
|
+
## A trava (herda a regra única do sandbox, endurecida)
|
|
33
|
+
|
|
34
|
+
> Só rode o Frieren contra um sistema **seu**, no **sandbox de dado falso** da
|
|
35
|
+
> `procedures/sandbox-dado-falso.md`. Nunca produção. Nunca dado de gente real.
|
|
36
|
+
> Nunca site de terceiro. Confirme os itens do checklist do sandbox antes de ligar.
|
|
37
|
+
|
|
38
|
+
## Como rodar (resumo, o guia oficial está no repositório da ferramenta)
|
|
39
|
+
|
|
40
|
+
1. Monte o sandbox de dado falso (procedimento próprio) e confirme o checklist.
|
|
41
|
+
2. Baixe e instale o Frieren seguindo o README oficial do projeto (repositório
|
|
42
|
+
público da KnowBe4). Use uma chave de LLM sua.
|
|
43
|
+
3. Suba o proxy, configure o navegador de teste para passar por ele, instale o
|
|
44
|
+
certificado SÓ nesse navegador/perfil.
|
|
45
|
+
4. Navegue pelo seu sistema no sandbox, logado. Os achados aparecem sozinhos.
|
|
46
|
+
5. Leia os achados e leve cada CONSERTO para o código (use os `kb/` do pacote para
|
|
47
|
+
traduzir e consertar). O conserto vai para produção; o teste, nunca.
|
|
48
|
+
|
|
49
|
+
## Depois
|
|
50
|
+
|
|
51
|
+
- Remova o certificado do navegador de teste.
|
|
52
|
+
- Desligue o proxy; não deixe o sandbox exposto com o proxy no ar.
|
|
53
|
+
- Reaudite lendo o código depois de consertar (a correção às vezes cria o defeito
|
|
54
|
+
seguinte).
|
|
55
|
+
|
|
56
|
+
## O que o Iron já faz sem isto
|
|
57
|
+
|
|
58
|
+
IDOR, segredo exposto, injeção de SQL, injeção de prompt e as demais famílias do
|
|
59
|
+
`kb/`: tudo isso a skill acha LENDO o código, sem proxy, sem certificado, sem mandar
|
|
60
|
+
dado para fora. Esta etapa é só para o teste ao vivo do que só existe em tempo de
|
|
61
|
+
execução.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# IDOR: um cliente vê o dado de outro
|
|
2
|
+
|
|
3
|
+
> Base de conhecimento do pacote **blindar-sistema**. Auditoria white-box: o Iron
|
|
4
|
+
> LÊ o código, não ataca de fora. Nenhum dado de cliente sai da máquina.
|
|
5
|
+
|
|
6
|
+
## O que é (linguagem de leigo)
|
|
7
|
+
|
|
8
|
+
IDOR (Insecure Direct Object Reference) é quando o sistema entrega um recurso pelo
|
|
9
|
+
seu identificador sem conferir se ele pertence a quem está pedindo. O usuário troca
|
|
10
|
+
`pedido/1042` por `pedido/1043` e vê o pedido do vizinho: nome, endereço, o que
|
|
11
|
+
comprou. O sistema entregou o dado sozinho, ninguém precisou invadir.
|
|
12
|
+
|
|
13
|
+
É a falha número 1 do OWASP (Broken Access Control) e a mais comum em app de vibe
|
|
14
|
+
coding, porque a IA constrói a rota que busca pelo ID e quase nunca adiciona sozinha
|
|
15
|
+
a checagem "esse ID é desta pessoa?".
|
|
16
|
+
|
|
17
|
+
## Onde procurar no código
|
|
18
|
+
|
|
19
|
+
O Iron abre os **handlers de rota** (onde o servidor responde um pedido) e procura
|
|
20
|
+
todo ponto que:
|
|
21
|
+
1. Recebe um identificador vindo de fora: `params`, query string, corpo do pedido,
|
|
22
|
+
segmento da URL (`/pedido/:id`), ou variável de GraphQL.
|
|
23
|
+
2. Usa esse identificador para buscar no banco (`select ... where id = :id`,
|
|
24
|
+
`findById`, `.get(id)`, `from('pedidos').eq('id', id)`).
|
|
25
|
+
3. **Não filtra também pelo dono** logado (`user_id`, `owner_id`, `account_id`).
|
|
26
|
+
|
|
27
|
+
Alvos por stack (onde o handler mora):
|
|
28
|
+
- **Next.js / API routes:** `app/api/**/route.ts`, `pages/api/**`.
|
|
29
|
+
- **Supabase:** a checagem de dono deve estar na **RLS (Row Level Security)** da
|
|
30
|
+
tabela, não só na query. Tabela sem RLS é IDOR aberto por padrão.
|
|
31
|
+
- **Express / Node:** arquivos de `routes/` e `controllers/`.
|
|
32
|
+
- **GraphQL:** cada **resolver** que recebe um `id` como argumento.
|
|
33
|
+
|
|
34
|
+
## Padrão vulnerável vs. seguro
|
|
35
|
+
|
|
36
|
+
VULNERÁVEL, busca só pelo ID:
|
|
37
|
+
```
|
|
38
|
+
// Express: confia no ID e devolve
|
|
39
|
+
const pedido = await db.pedido.findUnique({ where: { id: req.params.id } })
|
|
40
|
+
res.json(pedido)
|
|
41
|
+
```
|
|
42
|
+
```
|
|
43
|
+
// Supabase sem RLS: isto retorna QUALQUER pedido
|
|
44
|
+
const { data } = await supabase.from('pedidos').select('*').eq('id', id)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
SEGURO, o dono entra sempre na condição, no servidor:
|
|
48
|
+
```
|
|
49
|
+
// Express: o pedido só é achado se for do usuário logado
|
|
50
|
+
const pedido = await db.pedido.findFirst({
|
|
51
|
+
where: { id: req.params.id, userId: req.user.id }
|
|
52
|
+
})
|
|
53
|
+
if (!pedido) return res.status(404).end() // 404, não 403: não revela que existe
|
|
54
|
+
```
|
|
55
|
+
```sql
|
|
56
|
+
-- Supabase: a trava vive no banco, vale para toda query
|
|
57
|
+
alter table pedidos enable row level security;
|
|
58
|
+
create policy "dono le o proprio pedido"
|
|
59
|
+
on pedidos for select
|
|
60
|
+
using (auth.uid() = user_id);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Caso mais perigoso: escalada horizontal entre contas
|
|
64
|
+
|
|
65
|
+
Não é só trocar o número. Às vezes a rota confere o dono, mas **uma sessão consegue
|
|
66
|
+
usar o recurso de outra**. Teste white-box: existe alguma rota que muda estado
|
|
67
|
+
(cancelar, aprovar, transferir) e checa só se está logado, não se é o dono daquele
|
|
68
|
+
recurso específico? Isso é a categoria BOLA (acesso a objeto de outro usuário); a
|
|
69
|
+
variante em que a falha está numa FUNÇÃO administrativa se chama BFLA. As duas somem
|
|
70
|
+
no meio de painéis admin.
|
|
71
|
+
|
|
72
|
+
## Como confirmar (sem atacar produção)
|
|
73
|
+
|
|
74
|
+
1. Liste as rotas que recebem um ID de fora (a Fase 0 da skill já mapeou).
|
|
75
|
+
2. Para cada uma, responda por escrito: **"o dono do recurso é conferido no
|
|
76
|
+
servidor, nesta query?"** Se a resposta for "a tela só mostra o botão para o
|
|
77
|
+
dono", está decorativo, não protege. Quem chama a rota direto ignora a tela.
|
|
78
|
+
3. Em Supabase, liste as tabelas do schema público e cruze com as que têm policy:
|
|
79
|
+
`select tablename from pg_tables where schemaname = 'public'`. Tabela com dado de
|
|
80
|
+
cliente e sem RLS é achado crítico.
|
|
81
|
+
4. Prova viva (só no sandbox de dado falso, Fase 4): crie dois usuários de teste,
|
|
82
|
+
pegue um ID do usuário A e tente lê-lo logado como B. Abriu, é IDOR.
|
|
83
|
+
|
|
84
|
+
## O conserto
|
|
85
|
+
|
|
86
|
+
- A autorização vive **no servidor e/ou no banco**, nunca na interface.
|
|
87
|
+
- Toda query de recurso de usuário carrega o dono na condição (`where dono = logado`).
|
|
88
|
+
- Em Supabase, **RLS ligada em toda tabela com dado de gente**, com policy por
|
|
89
|
+
operação (select/insert/update/delete).
|
|
90
|
+
- Recurso não encontrado responde **404**, não 403: 403 confirma que o ID existe.
|
|
91
|
+
- IDs sequenciais (1, 2, 3) facilitam a varredura; UUID não conserta o IDOR, mas
|
|
92
|
+
para a enumeração em massa. Corrija a autorização primeiro, troque para UUID depois.
|
|
93
|
+
|
|
94
|
+
**Severidade:** crítico quando expõe dado de cliente, segredo ou dá acesso sem login.
|