wize-dev-kit 0.6.0 → 0.7.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/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ Format inspired by [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.7.0] — 2026-06-21
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Post-scan remediation planning (security-overlay).** Ao fim do `wize-sec-pentest`, o overlay traduz os findings em um backlog de correção pronto para `wize-create-epics-and-stories`.
|
|
13
|
+
- **`security-backlog.md`** gerado em `.wize/security/`: findings agrupados por tema (ex.: 97 secrets → 1 epic de rotação, não 97 stories), priorizados **P0/P1/P2** pela pior severidade do grupo, estimados S/M/L, com rastreabilidade aos findings de origem + `scope_sha256` e DoD ("re-rodar scan e confirmar finding ausente").
|
|
14
|
+
- Epics semeados pelo action plan do `ai-insights.json` quando presente.
|
|
15
|
+
- **Call-to-action** com o comando exato (`/wize-create-epics-and-stories --from .wize/security/security-backlog.md`) impresso no terminal, no `report.md` e como banner no `report.html`.
|
|
16
|
+
- Mantém **zero runtime próprio**: o overlay gera o backlog e imprime o comando; o usuário/agente é quem executa a skill de planejamento (o Node nunca invoca skills).
|
|
17
|
+
|
|
8
18
|
## [0.6.0] — 2026-06-20
|
|
9
19
|
|
|
10
20
|
### Added
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "wize-dev-kit",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"description": "Full-lifecycle AI-assisted development kit with Test Architect and Whiteport Design Studio embedded. Inspired by BMAD Method and WDS.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"ai",
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// backlog.js — turns classified security findings into a remediation
|
|
4
|
+
// backlog (epics + stories) that wize-create-epics-and-stories can consume.
|
|
5
|
+
//
|
|
6
|
+
// Design (per brief-post-scan):
|
|
7
|
+
// - Group findings by theme/section, NOT 1-story-per-finding (97 secrets
|
|
8
|
+
// => 1 "rotate secrets" story, not 97).
|
|
9
|
+
// - Priority P0/P1/P2 derived from the worst severity in the group.
|
|
10
|
+
// - Each story keeps traceability to the source findings + scope hash.
|
|
11
|
+
// - Seed epics from the AI action plan (ai-insights.json) when present.
|
|
12
|
+
// - Zero-dep, file-first. The overlay never invokes a skill — it prints
|
|
13
|
+
// a clear CTA command for the user/agent to run.
|
|
14
|
+
|
|
15
|
+
const CTA_COMMAND = '/wize-create-epics-and-stories --from .wize/security/security-backlog.md';
|
|
16
|
+
|
|
17
|
+
// Severity -> remediation priority.
|
|
18
|
+
function priorityFor(severity) {
|
|
19
|
+
switch (severity) {
|
|
20
|
+
case 'Critical':
|
|
21
|
+
case 'High':
|
|
22
|
+
return 'P0';
|
|
23
|
+
case 'Medium':
|
|
24
|
+
return 'P1';
|
|
25
|
+
default:
|
|
26
|
+
return 'P2'; // Low, Info-surface, None, unknown
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Group size -> rough estimate.
|
|
31
|
+
function estimateFor(count) {
|
|
32
|
+
if (count <= 2) return 'S';
|
|
33
|
+
if (count <= 10) return 'M';
|
|
34
|
+
return 'L';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Human label + remediation intent per section.
|
|
38
|
+
const SECTION_THEME = {
|
|
39
|
+
secrets: { theme: 'Rotacionar e remover segredos expostos', owasp: 'A07:2021' },
|
|
40
|
+
deps: { theme: 'Atualizar dependências vulneráveis', owasp: 'A06:2021' },
|
|
41
|
+
nuclei: { theme: 'Corrigir vulnerabilidades detectadas (nuclei)', owasp: null },
|
|
42
|
+
nikto: { theme: 'Hardening de servidor web (nikto)', owasp: 'A05:2021' },
|
|
43
|
+
sqlmap: { theme: 'Corrigir injeção SQL', owasp: 'A03:2021' },
|
|
44
|
+
ffuf: { theme: 'Revisar endpoints/arquivos descobertos', owasp: 'A05:2021' },
|
|
45
|
+
tech: { theme: 'Hardening: ocultar fingerprinting de versões', owasp: 'A05:2021' },
|
|
46
|
+
open_ports: { theme: 'Revisar exposição de portas/serviços', owasp: 'A05:2021' },
|
|
47
|
+
surface: { theme: 'Revisar superfície HTTP exposta', owasp: 'A05:2021' }
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Severity rank for "worst in group".
|
|
51
|
+
const SEV_RANK = { Critical: 5, High: 4, Medium: 3, Low: 2, 'Info-surface': 1, None: 0, unknown: 0 };
|
|
52
|
+
|
|
53
|
+
// groupFindings(findings) -> [{ section, theme, owasp, count, priority,
|
|
54
|
+
// worstSeverity, findings: [...] }] sorted by priority then count.
|
|
55
|
+
function groupFindings(findings) {
|
|
56
|
+
const bySection = {};
|
|
57
|
+
for (const f of findings || []) {
|
|
58
|
+
const s = f.section || 'unknown';
|
|
59
|
+
if (!bySection[s]) bySection[s] = [];
|
|
60
|
+
bySection[s].push(f);
|
|
61
|
+
}
|
|
62
|
+
const groups = [];
|
|
63
|
+
for (const [section, fs] of Object.entries(bySection)) {
|
|
64
|
+
let worst = 'unknown';
|
|
65
|
+
for (const f of fs) {
|
|
66
|
+
if ((SEV_RANK[f.severity] || 0) > (SEV_RANK[worst] || 0)) worst = f.severity;
|
|
67
|
+
}
|
|
68
|
+
const theme = (SECTION_THEME[section] && SECTION_THEME[section].theme) || `Revisar findings de ${section}`;
|
|
69
|
+
const owasp = SECTION_THEME[section] && SECTION_THEME[section].owasp;
|
|
70
|
+
groups.push({
|
|
71
|
+
section,
|
|
72
|
+
theme,
|
|
73
|
+
owasp,
|
|
74
|
+
count: fs.length,
|
|
75
|
+
worstSeverity: worst,
|
|
76
|
+
priority: priorityFor(worst),
|
|
77
|
+
findings: fs
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// Order: P0 first, then by count desc.
|
|
81
|
+
const pOrder = { P0: 0, P1: 1, P2: 2 };
|
|
82
|
+
groups.sort((a, b) => (pOrder[a.priority] - pOrder[b.priority]) || (b.count - a.count));
|
|
83
|
+
return groups;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function escapeMd(s) {
|
|
87
|
+
return String(s == null ? '' : s);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// buildBacklog({ findings, actionPlan, scopeSha, generatedAt }) -> markdown.
|
|
91
|
+
function buildBacklog({ findings = [], actionPlan = [], scopeSha = '', generatedAt = '' } = {}) {
|
|
92
|
+
const groups = groupFindings(findings);
|
|
93
|
+
const lines = [];
|
|
94
|
+
|
|
95
|
+
lines.push('---');
|
|
96
|
+
lines.push('kind: security-remediation-backlog');
|
|
97
|
+
lines.push('owner: red-teamer');
|
|
98
|
+
lines.push(`source_scope_sha256: ${escapeMd(scopeSha)}`);
|
|
99
|
+
lines.push(`generated_at: ${escapeMd(generatedAt)}`);
|
|
100
|
+
lines.push('consumed_by: wize-create-epics-and-stories');
|
|
101
|
+
lines.push('---');
|
|
102
|
+
lines.push('');
|
|
103
|
+
lines.push('# Security Remediation Backlog');
|
|
104
|
+
lines.push('');
|
|
105
|
+
lines.push('Backlog de correção derivado do scan de segurança. Cada epic agrupa findings por tema; cada story rastreia os findings de origem. Prioridade vem da severidade (P0 = Critical/High, P1 = Medium, P2 = Low/informativo).');
|
|
106
|
+
lines.push('');
|
|
107
|
+
lines.push(`> **Próximo passo:** rode \`${CTA_COMMAND}\` para transformar este backlog em stories formais.`);
|
|
108
|
+
lines.push('');
|
|
109
|
+
|
|
110
|
+
// Action plan summary (from AI insights) — the executive framing.
|
|
111
|
+
if (actionPlan && actionPlan.length) {
|
|
112
|
+
lines.push('## Plano de ação (resumo)');
|
|
113
|
+
lines.push('');
|
|
114
|
+
for (const a of actionPlan) {
|
|
115
|
+
lines.push(`- **[${escapeMd(a.priority)}] ${escapeMd(a.title)}** — ${escapeMd(a.detail)}`);
|
|
116
|
+
}
|
|
117
|
+
lines.push('');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Build a lookup from action plan title keywords to attach detail to epics.
|
|
121
|
+
const planByTheme = {};
|
|
122
|
+
for (const a of (actionPlan || [])) planByTheme[(a.title || '').toLowerCase()] = a;
|
|
123
|
+
|
|
124
|
+
const actionable = groups.filter(g => g.worstSeverity !== 'Info-surface' || g.priority !== 'P2' ? true : true);
|
|
125
|
+
|
|
126
|
+
if (groups.length === 0) {
|
|
127
|
+
lines.push('## (sem itens)');
|
|
128
|
+
lines.push('');
|
|
129
|
+
lines.push('_Nenhum finding acionável neste scan — no actionable findings._');
|
|
130
|
+
return lines.join('\n');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// One epic per group.
|
|
134
|
+
let epicN = 0;
|
|
135
|
+
for (const g of groups) {
|
|
136
|
+
epicN++;
|
|
137
|
+
const est = estimateFor(g.count);
|
|
138
|
+
// Find a matching action-plan detail by theme keyword overlap.
|
|
139
|
+
let planDetail = '';
|
|
140
|
+
for (const [title, a] of Object.entries(planByTheme)) {
|
|
141
|
+
const key = g.section;
|
|
142
|
+
if (title.includes(key) || (key === 'secrets' && title.includes('segredo')) ||
|
|
143
|
+
(key === 'deps' && (title.includes('depend') || title.includes('dep'))) ||
|
|
144
|
+
(key === 'tech' && title.includes('fingerprint'))) {
|
|
145
|
+
planDetail = a.detail; break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
lines.push(`## Epic ${String(epicN).padStart(2, '0')}: ${g.theme} [${g.priority}]`);
|
|
149
|
+
lines.push('');
|
|
150
|
+
lines.push(`- **Prioridade:** ${g.priority} (pior severidade: ${g.worstSeverity})`);
|
|
151
|
+
lines.push(`- **Findings cobertos:** ${g.count}`);
|
|
152
|
+
if (g.owasp) lines.push(`- **OWASP:** ${g.owasp}`);
|
|
153
|
+
lines.push(`- **Estimativa:** ${est}`);
|
|
154
|
+
if (planDetail) lines.push(`- **Como corrigir:** ${planDetail}`);
|
|
155
|
+
lines.push('');
|
|
156
|
+
lines.push('### Stories');
|
|
157
|
+
lines.push('');
|
|
158
|
+
// For large groups, a single remediation story + a verification story.
|
|
159
|
+
lines.push(`- **${g.theme}** (${g.priority}, est ${est}) — corrigir os ${g.count} finding(s) de \`${g.section}\`. _Origem: ${g.section} (${g.count} findings, ${g.worstSeverity})._`);
|
|
160
|
+
lines.push(`- **Verificar correção de ${g.section}** (${g.priority}, est S) — re-rodar \`/wize-sec-pentest\` e confirmar que os findings de \`${g.section}\` sumiram (DoD).`);
|
|
161
|
+
lines.push('');
|
|
162
|
+
// Sample of source findings for traceability (cap at 5).
|
|
163
|
+
const sample = g.findings.slice(0, 5);
|
|
164
|
+
lines.push('<details><summary>Findings de origem (amostra)</summary>');
|
|
165
|
+
lines.push('');
|
|
166
|
+
for (const f of sample) {
|
|
167
|
+
lines.push(`- ${escapeMd(f.raw)}`);
|
|
168
|
+
}
|
|
169
|
+
if (g.findings.length > sample.length) {
|
|
170
|
+
lines.push(`- _… e mais ${g.findings.length - sample.length}._`);
|
|
171
|
+
}
|
|
172
|
+
lines.push('');
|
|
173
|
+
lines.push('</details>');
|
|
174
|
+
lines.push('');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return lines.join('\n');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = { buildBacklog, priorityFor, estimateFor, groupFindings, CTA_COMMAND };
|
|
@@ -147,6 +147,16 @@ async function main() {
|
|
|
147
147
|
if (result.skipped.length) {
|
|
148
148
|
console.log(`! skipped: ${result.skipped.join(', ')}`);
|
|
149
149
|
}
|
|
150
|
+
// Next-step CTA: turn the findings into a remediation sprint. The overlay
|
|
151
|
+
// never invokes a skill itself (zero runtime própio) — it prints the exact
|
|
152
|
+
// command for the user/agent to run.
|
|
153
|
+
try {
|
|
154
|
+
const { CTA_COMMAND } = require('../../../_shared/backlog.js');
|
|
155
|
+
console.log('');
|
|
156
|
+
console.log('→ Backlog de correção gerado em .wize/security/security-backlog.md');
|
|
157
|
+
console.log('→ Próximo passo — planejar a sprint de correção, rode:');
|
|
158
|
+
console.log(` ${CTA_COMMAND}`);
|
|
159
|
+
} catch (_) { /* backlog module optional */ }
|
|
150
160
|
process.exit(result.ok ? 0 : 1);
|
|
151
161
|
}
|
|
152
162
|
|
|
@@ -470,15 +470,36 @@ function renderReport({ securityDir } = {}) {
|
|
|
470
470
|
lines.push('');
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
+
// Next step CTA — turn findings into a remediation sprint.
|
|
474
|
+
{
|
|
475
|
+
const { CTA_COMMAND } = require('../../../_shared/backlog.js');
|
|
476
|
+
lines.push('## Próximo passo — planejar a correção');
|
|
477
|
+
lines.push('');
|
|
478
|
+
lines.push('Um backlog de remediação foi gerado em `.wize/security/security-backlog.md` (epics/stories priorizados a partir destes findings).');
|
|
479
|
+
lines.push('');
|
|
480
|
+
lines.push('Para transformá-lo em uma sprint formal, rode:');
|
|
481
|
+
lines.push('');
|
|
482
|
+
lines.push('```');
|
|
483
|
+
lines.push(CTA_COMMAND);
|
|
484
|
+
lines.push('```');
|
|
485
|
+
lines.push('');
|
|
486
|
+
}
|
|
487
|
+
|
|
473
488
|
// Final newline + deterministic timestamp is the only line that
|
|
474
489
|
// changes between runs in tests; the rest of the report is stable.
|
|
475
490
|
const reportPath = path.join(sec, 'report.md');
|
|
476
491
|
fs.writeFileSync(reportPath, lines.join('\n'), 'utf8');
|
|
477
492
|
|
|
493
|
+
// Generate the remediation backlog (consumable by
|
|
494
|
+
// wize-create-epics-and-stories) from the same findings + AI action plan.
|
|
495
|
+
const { buildBacklog, CTA_COMMAND } = require('../../../_shared/backlog.js');
|
|
496
|
+
const backlogMd = buildBacklog({ findings: allFindings, actionPlan, scopeSha, generatedAt });
|
|
497
|
+
fs.writeFileSync(path.join(sec, 'security-backlog.md'), backlogMd, 'utf8');
|
|
498
|
+
|
|
478
499
|
// Also generate the HTML report (self-contained, no remote refs).
|
|
479
|
-
renderReportHtml({ securityDir: sec, phaseSummaries, allFindings, refusals, generatedAt, scopeSha, risk, coverage, briefing, actionPlan });
|
|
500
|
+
renderReportHtml({ securityDir: sec, phaseSummaries, allFindings, refusals, generatedAt, scopeSha, risk, coverage, briefing, actionPlan, ctaCommand: CTA_COMMAND });
|
|
480
501
|
|
|
481
|
-
return { ok: true, findings: allFindings.length };
|
|
502
|
+
return { ok: true, findings: allFindings.length, backlog: 'security-backlog.md', cta: CTA_COMMAND };
|
|
482
503
|
}
|
|
483
504
|
|
|
484
505
|
// --- HTML report -------------------------------------------------------
|
|
@@ -744,10 +765,17 @@ footer.site code { background: var(--code-bg); padding: 1px 6px; border-radius:
|
|
|
744
765
|
.card .rec { margin: .75rem 0 0; padding: .6rem .8rem; background: var(--owasp-bg); border-radius: var(--radius-sm); font-size: .85rem; color: var(--fg); }
|
|
745
766
|
.card .rec strong { color: var(--owasp); }
|
|
746
767
|
|
|
768
|
+
/* Call to action — next-step sprint planning */
|
|
769
|
+
.cta { background: var(--bg-elev); border: 1px solid var(--accent); border-radius: var(--radius); padding: 1rem 1.25rem; }
|
|
770
|
+
.cta h2 { margin: 0 0 .5rem; font-size: 1.05rem; color: var(--accent); }
|
|
771
|
+
.cta p { margin: 0 0 .5rem; font-size: .92rem; }
|
|
772
|
+
.cta .cta-cmd { background: var(--code-bg); border: 1px dashed var(--accent); border-radius: var(--radius-sm); padding: .75rem 1rem; margin: 0; overflow-x: auto; }
|
|
773
|
+
.cta .cta-cmd code { font-family: ui-monospace, "SF Mono", Menlo, monospace; color: var(--fg); user-select: all; }
|
|
774
|
+
|
|
747
775
|
@media print {
|
|
748
776
|
header.site { position: static; }
|
|
749
777
|
.skip-link, .filters { display: none; }
|
|
750
|
-
.card, .risk-banner, .briefing, .action-plan .plan li { break-inside: avoid; box-shadow: none; border-color: #888; }
|
|
778
|
+
.card, .risk-banner, .briefing, .action-plan .plan li, .cta { break-inside: avoid; box-shadow: none; border-color: #888; }
|
|
751
779
|
}
|
|
752
780
|
`;
|
|
753
781
|
|
|
@@ -760,13 +788,14 @@ function escapeHtml(s) {
|
|
|
760
788
|
.replace(/'/g, ''');
|
|
761
789
|
}
|
|
762
790
|
|
|
763
|
-
function renderReportHtml({ securityDir, phaseSummaries, allFindings, refusals, generatedAt, scopeSha, risk, coverage, briefing, actionPlan }) {
|
|
791
|
+
function renderReportHtml({ securityDir, phaseSummaries, allFindings, refusals, generatedAt, scopeSha, risk, coverage, briefing, actionPlan, ctaCommand }) {
|
|
764
792
|
const sec = securityDir;
|
|
765
793
|
const title = `Security Report — ${scopeSha ? scopeSha.slice(0, 12) : 'unknown'}`;
|
|
766
794
|
risk = risk || computeRisk(allFindings);
|
|
767
795
|
coverage = coverage || computeCoverage(loadPartial, sec);
|
|
768
796
|
briefing = briefing || heuristicBriefing(risk, coverage, allFindings);
|
|
769
797
|
actionPlan = (actionPlan && actionPlan.length) ? actionPlan : heuristicActionPlan(allFindings, coverage);
|
|
798
|
+
ctaCommand = ctaCommand || require('../../../_shared/backlog.js').CTA_COMMAND;
|
|
770
799
|
|
|
771
800
|
// Severity counts (for sticky header). 'Info-surface' is folded into a
|
|
772
801
|
// dedicated "surface" bucket so the stakeholder header isn't dominated by
|
|
@@ -949,6 +978,11 @@ ${actionPlan.map(a => ` <li class="prio-${escapeHtml((a.priority||'').toLow
|
|
|
949
978
|
` <div class="findings">${findingsHtml}</div>`,
|
|
950
979
|
` </section>`,
|
|
951
980
|
refusalsHtml,
|
|
981
|
+
` <section class="cta" aria-labelledby="cta-h">
|
|
982
|
+
<h2 id="cta-h">Próximo passo — planejar a correção</h2>
|
|
983
|
+
<p>Um backlog de remediação foi gerado em <code>.wize/security/security-backlog.md</code> (epics/stories priorizados a partir destes findings). Para transformá-lo em uma sprint formal, rode:</p>
|
|
984
|
+
<pre class="cta-cmd"><code>${escapeHtml(ctaCommand)}</code></pre>
|
|
985
|
+
</section>`,
|
|
952
986
|
`</main>`,
|
|
953
987
|
`<footer class="site" role="contentinfo">`,
|
|
954
988
|
` <p>Gerado por <code>wize-sec-report</code> (overlay <code>security-overlay</code>) · Self-contained (sem refs remotas) · CSS inline · Default: dark mode.</p>`,
|