Neue Skills, Referenzen & OpenWiki-Doku integriert

Umfangreiche Erweiterung der Skill-Bibliothek: Neue Skills für Humanisierung (Englisch/PT-BR), Design-Validierung, AI-SEO und Coolify-Deployment inkl. Regelwerke, Presets, Pattern-Referenzen, Testfälle und Automatisierungsskripte. Zusätzliche Skills für Revenue-Centric Design, Pier Cloud, OKF, Lebenslauf- und LinkedIn-Optimierung sowie zahlreiche Referenzdateien, Checklisten und YAML/JSON/Markdown-Templates. Einführung einer vollständigen OpenWiki-Dokumentation mit Architektur-, Domain- und Workflow-Beschreibungen, zentralem Index und automatisierten Updates. Modularer Aufbau, restriktive Lizenzen und umfassende Qualitäts- und Evaluationsmechanismen für alle neuen Inhalte.
This commit is contained in:
Tim Krampitz
2026-07-26 14:00:58 +02:00
parent 070727d5cd
commit 01046b01e4
202 changed files with 31290 additions and 0 deletions

View File

@@ -0,0 +1,201 @@
# Finding Format Specification
Este documento define a estrutura canônica de um security finding. Todo finding produzido pelo security-specialist DEVE conformar a este schema.
---
## Formato Simples (SQLite — uso interno)
Para persistência no scan.db e workflows modulares (discovery, triage, diff-review):
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | UUID v4 | yes | Identificador único |
| `scan_id` | UUID v4 | yes | FK para scan session parent |
| `title` | string | yes | Nome da vulnerabilidade (≤ 80 chars) |
| `severity` | enum | yes | `critical`, `high`, `medium`, `low`, `info` |
| `category` | enum | yes | `injection`, `xss`, `auth`, `crypto`, `exposure`, `config`, `dependency`, `logic`, `other` |
| `status` | enum | yes | `open`, `fixed`, `false-positive`, `accepted-risk`, `tracked` |
| `file_path` | string | yes | Repo-relative path |
| `line_number` | integer | yes | Line onde a vulnerabilidade origina |
| `description` | string | yes | 24 frases explicando o quê e por quê importa |
| `evidence` | string | yes | Código, data flow trace, ou PoC |
| `remediation` | string | no | Fix sugerido |
| `tracking_url` | string | no | URL do issue tracker externo |
| `notes` | string | no | Notas de triage |
| `created_at` | string | yes | ISO 8601 timestamp |
---
## Formato Estruturado (JSON — output de full-scan pipeline)
Para findings que passam pela pipeline completa de 6 fases (full-scan), use o formato rico definido em `report-schema.json`. Este formato é **obrigatório** para o output `findings.json` do full-scan.
### Campos do Formato Estruturado
| Field | Description |
|-------|-------------|
| `verdict` | `confirmed` ou `rejected` |
| `title` | Título conciso e padronizado |
| `description` | Explicação completa com detalhes de reprodução |
| `root_cause` | Template: `[function] em [file] não [ação], permitindo [consequência]` |
| `intended_behavior` | O que o dev tentou construir (lógica não-vulnerável) |
| `trace` | Array sequencial: `entrypoint``propagation`* → `sink` |
| `conditions` | Pré-requisitos factuais para exploração |
| `execution` | Perspectiva do atacante, payloads, instruções, resultado esperado |
| `remediation` | Estratégia + code_changes opcionais |
| `severity` | Likelihood × Impact, cada com score + reason |
| `confidence` | Score (low/medium/high) + reason |
### Trace
Cada step do trace contém:
```json
{
"kind": "entrypoint|propagation|sink",
"file": "src/routes/users.js",
"line": 42,
"scope": "searchUsers",
"description": "User input from query param 'q' enters the handler"
}
```
**Regras:**
- Mínimo 2 steps (entrypoint + sink)
- Primeiro step DEVE ser `kind: "entrypoint"`
- Último step DEVE ser `kind: "sink"`
- File paths relativos à raiz do repositório
- Scope é function/method name sem parênteses
### Conditions
Pré-requisitos factuais. Array vazio = explorável por default.
```json
{
"kind": "authentication_level",
"description": "Requires authenticated session with any role"
}
```
Kinds válidos: `authentication_level`, `authorization_role`, `user_interaction`, `system_configuration`, `network_routing`, `environmental_dependency`, `data_state`, `timing_dependency`, `third_party_dependency`
### Execution
```json
{
"attacker_perspective": "Authenticated user with basic role",
"payloads": ["GET /api/search?q=' UNION SELECT password FROM users--"],
"instructions": [
"Login with any valid account",
"Navigate to search endpoint",
"Inject SQL via query parameter"
],
"expected_result": "Response contains all user password hashes"
}
```
### Confidence
```json
{
"score": "high",
"reason": "Full trace verified against source. All steps readable and confirmed."
}
```
- **high**: Trace completo verificado, exploit testável
- **medium**: Trace parcialmente verificado, algumas assumptions
- **low**: Static analysis only, complex routing, missing files
---
## Quando Usar Qual Formato
| Workflow | Formato |
|----------|---------|
| `full-scan` (pipeline 6 fases) | **Estruturado** (findings.json validado contra schema) |
| `discovery`, `diff-review`, `triage` | **Simples** (SQLite) |
| `pentest` | **Simples** (SQLite) + evidence expandida |
| `reporting` (HTML final) | Ambos — HTML renderiza de qualquer fonte |
---
## Validação
Para o formato estruturado, valide com:
```bash
node scripts/validate-findings.cjs .security/scans/<timestamp>/findings.json
```
O validador checa: required fields, enum values, structural constraints, `additionalProperties`, e semantic rules (trace starts at entrypoint, ends at sink).
---
## Exemplo: Formato Estruturado Completo
```json
{
"verdict": "confirmed",
"title": "SQL Injection in user search endpoint",
"description": "User-supplied search parameter is concatenated directly into SQL query. Authenticated user can extract arbitrary data including credentials.",
"root_cause": "searchUsers in src/routes/users.js does not parameterize user input, allowing arbitrary SQL execution.",
"intended_behavior": "Search should filter users by name using parameterized queries, returning only matching records the caller is authorized to see.",
"trace": [
{
"kind": "entrypoint",
"file": "src/routes/users.js",
"line": 35,
"scope": "searchUsers",
"description": "User input from req.query.search enters handler"
},
{
"kind": "propagation",
"file": "src/routes/users.js",
"line": 42,
"scope": "searchUsers",
"description": "Input concatenated into SQL string template without escaping"
},
{
"kind": "sink",
"file": "src/routes/users.js",
"line": 43,
"scope": "searchUsers",
"description": "Concatenated string passed to db.raw() for execution"
}
],
"conditions": [
{
"kind": "authentication_level",
"description": "Requires valid session (any role)"
}
],
"execution": {
"attacker_perspective": "Authenticated user with basic role",
"payloads": ["GET /api/users?search=' UNION SELECT password FROM users--"],
"instructions": [
"Login with any valid account",
"Send GET request to /api/users with crafted search parameter",
"Observe response containing all password hashes"
],
"expected_result": "Response body contains password hashes for all users in database"
},
"remediation": {
"strategy": "Use parameterized queries via the ORM's query builder instead of string concatenation.",
"code_changes": [
{
"file_name": "src/routes/users.js",
"fixed_code": "const results = await db('users').where('name', 'like', `%${search}%`);"
}
]
},
"severity": {
"likelihood": { "score": "high", "reason": "Any authenticated user can exploit. No special tools needed." },
"impact": { "score": "critical", "reason": "Full database read access including credentials and PII." },
"overall_severity": "critical"
},
"confidence": {
"score": "high",
"reason": "Full trace verified. db.raw() confirmed at line 43. No parameterization in path."
}
}
```

View File

@@ -0,0 +1,284 @@
# Report Format Specification
The final report is a **self-contained HTML file** (`security-report.html`). It opens in any browser, uses no external dependencies, and includes interactive features (collapsible sections, filters, color-coded severity).
---
## Output Format
Single HTML file with embedded CSS and JS. No external CDN, no build step. The report must work offline when opened with `file://`.
---
## Severity Color System
| Severity | Color | Badge HTML |
|----------|-------|-----------|
| Critical | `#dc2626` (red-600) | `<span class="badge badge-critical">CRITICAL</span>` |
| High | `#ea580c` (orange-600) | `<span class="badge badge-high">HIGH</span>` |
| Medium | `#ca8a04` (yellow-600) | `<span class="badge badge-medium">MEDIUM</span>` |
| Low | `#16a34a` (green-600) | `<span class="badge badge-low">LOW</span>` |
| Info | `#6b7280` (gray-500) | `<span class="badge badge-info">INFO</span>` |
---
## HTML Template
Generate the report using this structure. Replace `{{placeholders}}` with actual data.
```html
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Security Audit — {{repo-name}}</title>
<style>
:root {
--critical: #dc2626; --high: #ea580c; --medium: #ca8a04;
--low: #16a34a; --info: #6b7280; --pass: #16a34a; --fail: #dc2626;
--bg: #0f172a; --surface: #1e293b; --surface-2: #334155;
--text: #f1f5f9; --text-muted: #94a3b8; --border: #475569;
--code-bg: #0f172a; --accent: #3b82f6;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Inter', -apple-system, sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; padding: 2rem; max-width: 1200px; margin: 0 auto; }
h1 { font-size: 1.75rem; margin-bottom: 0.25rem; }
h2 { font-size: 1.35rem; margin: 2.5rem 0 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--border); }
h3 { font-size: 1.1rem; margin: 1.5rem 0 0.5rem; }
p, li { color: var(--text-muted); }
a { color: var(--accent); }
code { background: var(--code-bg); border: 1px solid var(--border); padding: 0.15em 0.4em; border-radius: 4px; font-size: 0.85em; }
pre { background: var(--code-bg); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; overflow-x: auto; margin: 0.75rem 0; }
pre code { border: none; padding: 0; background: none; }
/* Badges */
.badge { display: inline-block; padding: 0.2em 0.6em; border-radius: 4px; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: white; }
.badge-critical { background: var(--critical); }
.badge-high { background: var(--high); }
.badge-medium { background: var(--medium); }
.badge-low { background: var(--low); }
.badge-info { background: var(--info); }
.badge-pass { background: var(--pass); }
.badge-fail { background: var(--fail); }
/* Summary cards */
.summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 1rem; margin: 1rem 0; }
.summary-card { background: var(--surface); border-radius: 8px; padding: 1rem; text-align: center; border-left: 4px solid var(--border); }
.summary-card .count { font-size: 2rem; font-weight: 700; }
.summary-card .label { font-size: 0.8rem; color: var(--text-muted); text-transform: uppercase; }
.summary-card.critical { border-left-color: var(--critical); }
.summary-card.critical .count { color: var(--critical); }
.summary-card.high { border-left-color: var(--high); }
.summary-card.high .count { color: var(--high); }
.summary-card.medium { border-left-color: var(--medium); }
.summary-card.medium .count { color: var(--medium); }
.summary-card.low { border-left-color: var(--low); }
.summary-card.low .count { color: var(--low); }
.summary-card.info { border-left-color: var(--info); }
.summary-card.info .count { color: var(--info); }
/* Tables */
table { width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: 0.9rem; }
th, td { padding: 0.6rem 0.8rem; text-align: left; border-bottom: 1px solid var(--border); }
th { background: var(--surface); color: var(--text); font-weight: 600; position: sticky; top: 0; }
tr:hover { background: var(--surface); }
/* Finding cards */
.finding { background: var(--surface); border-radius: 8px; padding: 1.25rem; margin: 1rem 0; border-left: 4px solid var(--border); }
.finding.critical { border-left-color: var(--critical); }
.finding.high { border-left-color: var(--high); }
.finding.medium { border-left-color: var(--medium); }
.finding.low { border-left-color: var(--low); }
.finding.info { border-left-color: var(--info); }
.finding-header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.75rem; }
.finding-title { font-weight: 600; font-size: 1rem; }
.finding-meta { font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.5rem; }
/* Pentest results */
.pentest-item { background: var(--surface); border-radius: 8px; padding: 1rem; margin: 0.75rem 0; }
.pentest-item .result-pass { color: var(--pass); font-weight: 700; }
.pentest-item .result-fail { color: var(--fail); font-weight: 700; }
/* Collapsible */
details { margin: 0.5rem 0; }
details summary { cursor: pointer; padding: 0.5rem; border-radius: 4px; font-weight: 600; }
details summary:hover { background: var(--surface-2); }
details[open] summary { margin-bottom: 0.5rem; }
/* CVE table */
.cve-exploitable { color: var(--fail); font-weight: 700; }
.cve-not-exploitable { color: var(--pass); }
/* Filters */
.filters { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 1rem 0; }
.filter-btn { padding: 0.4em 0.8em; border-radius: 4px; border: 1px solid var(--border); background: var(--surface); color: var(--text-muted); cursor: pointer; font-size: 0.8rem; transition: 0.2s; }
.filter-btn:hover, .filter-btn.active { background: var(--accent); color: white; border-color: var(--accent); }
/* Metadata */
.meta-grid { display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 1rem; font-size: 0.9rem; margin: 1rem 0; }
.meta-grid dt { color: var(--text-muted); }
.meta-grid dd { color: var(--text); }
@media (max-width: 768px) {
body { padding: 1rem; }
.summary-grid { grid-template-columns: repeat(3, 1fr); }
}
</style>
</head>
<body>
<h1>🛡️ Security Audit Report</h1>
<dl class="meta-grid">
<dt>Repository</dt><dd>{{repo-name}}</dd>
<dt>Date</dt><dd>{{date}}</dd>
<dt>Target</dt><dd>{{target-urls}}</dd>
<dt>Methodology</dt><dd>SAST + DAST (localhost) + DAST (production) + Pentest</dd>
</dl>
<!-- Section: Summary Cards -->
<h2>Resumo</h2>
<div class="summary-grid">
<div class="summary-card critical"><div class="count">{{critical-count}}</div><div class="label">Critical</div></div>
<div class="summary-card high"><div class="count">{{high-count}}</div><div class="label">High</div></div>
<div class="summary-card medium"><div class="count">{{medium-count}}</div><div class="label">Medium</div></div>
<div class="summary-card low"><div class="count">{{low-count}}</div><div class="label">Low</div></div>
<div class="summary-card info"><div class="count">{{info-count}}</div><div class="label">Info</div></div>
</div>
<p>{{executive-summary-paragraph}}</p>
<!-- Section: Findings with filters -->
<h2>Achados</h2>
<div class="filters">
<button class="filter-btn active" onclick="filterFindings('all')">Todos</button>
<button class="filter-btn" onclick="filterFindings('critical')">Critical</button>
<button class="filter-btn" onclick="filterFindings('high')">High</button>
<button class="filter-btn" onclick="filterFindings('medium')">Medium</button>
<button class="filter-btn" onclick="filterFindings('low')">Low</button>
<button class="filter-btn" onclick="filterFindings('info')">Info</button>
</div>
<!-- Repeat this block for each finding -->
<div class="finding {{severity}}" data-severity="{{severity}}">
<div class="finding-header">
<span class="badge badge-{{severity}}">{{SEVERITY}}</span>
<span class="finding-title">{{finding-title}}</span>
</div>
<div class="finding-meta">📁 <code>{{file}}:{{line}}</code></div>
<p>{{description}}</p>
<details>
<summary>Evidência</summary>
<pre><code>{{evidence-code}}</code></pre>
</details>
<details>
<summary>Remediação</summary>
<p>{{remediation-text}}</p>
<pre><code>{{remediation-code}}</code></pre>
</details>
</div>
<!-- End finding block -->
<!-- Section: CVE Analysis -->
<h2>Análise de CVEs × Contexto do Projeto</h2>
<table>
<thead>
<tr><th>#</th><th>Advisory</th><th>Sev. Genérica</th><th>Precondição</th><th>Presente?</th><th>Sev. Real</th><th>Razão</th></tr>
</thead>
<tbody>
<!-- Repeat per CVE -->
<tr>
<td>{{n}}</td>
<td><a href="{{advisory-url}}">{{advisory-id}}</a></td>
<td><span class="badge badge-{{generic-sev}}">{{generic-sev}}</span></td>
<td>{{precondition}}</td>
<td class="{{cve-exploitable|cve-not-exploitable}}">{{yes-no}}</td>
<td><span class="badge badge-{{real-sev}}">{{real-sev}}</span></td>
<td>{{rationale}}</td>
</tr>
</tbody>
</table>
<!-- Section: Pentest Results -->
<h2>Pentest — Testes Ativos</h2>
<!-- Repeat per test -->
<div class="pentest-item">
<strong>P{{n}}: {{test-name}}</strong>
<span class="{{result-pass|result-fail}}">{{PASS|FAIL}}</span>
<details>
<summary>Detalhes</summary>
<p><strong>Objetivo:</strong> {{objective}}</p>
<pre><code>{{command-or-payload}}</code></pre>
<p><strong>Resposta:</strong> {{response-summary}}</p>
</details>
</div>
<!-- End pentest block -->
<!-- Section: Verified Secure -->
<h2>Verificado Seguro ✅</h2>
<table>
<thead><tr><th>Teste</th><th>Resultado</th><th>Evidência</th></tr></thead>
<tbody>
<!-- Repeat per negative finding -->
<tr>
<td>{{test-name}}</td>
<td><span class="badge badge-pass">PASS</span></td>
<td>{{evidence}}</td>
</tr>
</tbody>
</table>
<!-- Section: Recommendations -->
<h2>Remediação Prioritária</h2>
<table>
<thead><tr><th>#</th><th>Ação</th><th>Esforço</th><th>Impacto</th></tr></thead>
<tbody>
<!-- Repeat per recommendation -->
<tr><td>{{n}}</td><td>{{action}}</td><td>{{effort}}</td><td>{{impact}}</td></tr>
</tbody>
</table>
<script>
function filterFindings(severity) {
document.querySelectorAll('.finding').forEach(el => {
el.style.display = (severity === 'all' || el.dataset.severity === severity) ? '' : 'none';
});
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.toggle('active', btn.textContent.toLowerCase() === severity || (severity === 'all' && btn.textContent === 'Todos'));
});
}
</script>
<footer style="margin-top:4rem;padding-top:1.5rem;border-top:1px solid var(--border);text-align:center;font-size:0.8rem;color:var(--text-muted);">
Generated by <strong>security-specialist</strong> skill by <a href="https://github.com/fabricioctelles/skills" style="color:var(--accent);">github.com/fabricioctelles/skills</a>
</footer>
</body>
</html>
```
---
## Generation Rules
1. **Output a single `.html` file** — not markdown. Name it `security-report.html` in the repo root.
2. **Replace all `{{placeholders}}`** with actual data from the scan.
3. **Repeat blocks** as indicated by comments (`<!-- Repeat per finding -->`, etc.).
4. **Sort findings** by severity descending (critical first), then alphabetically.
5. **Collapsible evidence/remediation** — keeps the report scannable without hiding info.
6. **Filter buttons** — JS filters findings by severity interactively.
7. **Code in evidence** — use `<pre><code>` blocks, HTML-escape all special characters.
8. **Links in CVE table** — advisory IDs link to the GitHub advisory URL.
9. **No external dependencies** — no CDN fonts, no JS libs. Pure HTML/CSS/JS.
10. **Dark theme by default** — matches terminal-native developer workflows.
---
## Content Rules (unchanged from markdown era)
- Every finding needs source location, data flow trace, and concrete exploitability.
- Never truncate evidence to the point where it loses meaning.
- Keep descriptions factual. No speculative language.
- The report must be self-contained.
- Include ALL tests performed (pentest section), including those that passed.
- CVE analysis table is mandatory when dependency vulns exist.
- Negative results table is mandatory — reader needs to know what was tested and found secure.

View File

@@ -0,0 +1,126 @@
{
"$comment": "Schema para findings.json estruturado. validate-findings.cjs lê este arquivo diretamente.",
"output_schema": {
"oneOf": [
{
"type": "object",
"description": "Vulnerabilidade confirmada — report completo e verificado independentemente.",
"properties": {
"verdict": { "type": "string", "const": "confirmed" },
"title": { "type": "string", "description": "Título conciso e padronizado para a vulnerabilidade." },
"description": { "type": "string", "description": "Explicação completa da vulnerabilidade. Inclua detalhes de reprodução (PoC input, configuração, output observado) aqui." },
"root_cause": { "type": "string", "description": "Uma frase usando template: '[function_or_component] em [file] não [ação ausente], permitindo [consequência]'. DEVE incluir nome de function/component e file." },
"intended_behavior": { "type": "string", "description": "O que o dev tentou construir? Explique a lógica de negócio pretendida, não-vulnerável." },
"trace": {
"type": "array",
"minItems": 2,
"items": {
"type": "object",
"properties": {
"kind": { "type": "string", "enum": ["entrypoint", "propagation", "sink"] },
"file": { "type": "string", "description": "Caminho exato relativo à raiz do repositório." },
"line": { "type": "integer" },
"scope": { "type": "string", "description": "Nome de function ou method. Sem parênteses, sem argumentos." },
"description": { "type": "string", "description": "Descrição factual do state change ou data movement." }
},
"required": ["kind", "file", "line", "scope", "description"],
"additionalProperties": false
},
"description": "Trace sequencial do entrypoint ao sink, verificado contra source code real. Primeiro step deve ser kind 'entrypoint' e último deve ser kind 'sink'."
},
"conditions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"kind": { "type": "string", "enum": ["authentication_level", "authorization_role", "user_interaction", "system_configuration", "network_routing", "environmental_dependency", "data_state", "timing_dependency", "third_party_dependency"] },
"description": { "type": "string" }
},
"required": ["kind", "description"],
"additionalProperties": false
},
"description": "Pré-requisitos factuais para exploração. Array vazio se explorável por default."
},
"execution": {
"type": "object",
"properties": {
"attacker_perspective": { "type": "string", "description": "Quem é o atacante e seu starting point." },
"payloads": { "type": "array", "items": { "type": "string" }, "description": "Inputs maliciosos específicos, HTTP requests, ou scripts." },
"instructions": { "type": "array", "items": { "type": "string" }, "description": "Array linear de todas ações do atacante do setup até exploração." },
"expected_result": { "type": "string", "description": "Resultado observável confirmando exploração bem-sucedida." }
},
"required": ["attacker_perspective", "payloads", "instructions", "expected_result"],
"additionalProperties": false
},
"remediation": {
"type": "object",
"properties": {
"strategy": { "type": "string", "description": "Explicação high-level do fix." },
"code_changes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"file_name": { "type": "string" },
"fixed_code": { "type": "string" }
},
"required": ["file_name", "fixed_code"],
"additionalProperties": false
}
}
},
"required": ["strategy"],
"additionalProperties": false
},
"severity": {
"type": "object",
"properties": {
"likelihood": {
"type": "object",
"properties": {
"score": { "type": "string", "enum": ["informational", "low", "medium", "high", "critical"] },
"reason": { "type": "string" }
},
"required": ["score", "reason"],
"additionalProperties": false
},
"impact": {
"type": "object",
"properties": {
"score": { "type": "string", "enum": ["informational", "low", "medium", "high", "critical"] },
"reason": { "type": "string" }
},
"required": ["score", "reason"],
"additionalProperties": false
},
"overall_severity": { "type": "string", "enum": ["informational", "low", "medium", "high", "critical"] }
},
"required": ["likelihood", "impact", "overall_severity"],
"additionalProperties": false
},
"confidence": {
"type": "object",
"properties": {
"score": { "type": "string", "enum": ["low", "medium", "high"] },
"reason": { "type": "string", "description": "Por que você deu essa confidence. Mencione missing files, complex routing, ou ambiguous data flows." }
},
"required": ["score", "reason"],
"additionalProperties": false
}
},
"required": ["verdict", "title", "description", "root_cause", "intended_behavior", "trace", "conditions", "execution", "remediation", "severity", "confidence"],
"additionalProperties": false
},
{
"type": "object",
"description": "Finding rejeitado — o comportamento descrito é factualmente incorreto ou o code path não existe.",
"properties": {
"verdict": { "type": "string", "const": "rejected" },
"reason": { "type": "string", "description": "Explique quais claims específicos no finding estão factualmente errados." }
},
"required": ["verdict", "reason"],
"additionalProperties": false
}
]
}
}

View File

@@ -0,0 +1,164 @@
# Scan Artifacts Specification
This document describes the file layout and purpose of each artifact produced by a completed security scan.
---
## Directory Structure
All scan artifacts live in a `.security/` directory at the repository root:
```
.security/
├── scan.db # SQLite database (source of truth)
├── findings.json # Exported findings — simple format (generated by finalize.py)
├── report.md # Human-readable report (generated by finalize.py)
├── integrity.sha256 # SHA-256 of findings.json (tamper detection)
├── threat-model.md # Repository threat model (if generated)
└── scans/
└── <timestamp>/
├── architecture.md # Phase 1 output (full-scan only)
├── findings.json # Structured format — validated against report-schema.json
├── security-report.html # Self-contained HTML report
├── report.json # Machine-readable summary
└── manifest.json # File hashes + completion timestamp
```
---
## Artifact Descriptions
### scan.db — Source of Truth
A SQLite database containing the complete scan state. This is the authoritative data store that all other artifacts are derived from.
**Tables:**
- `scans` — Scan metadata (id, repo, branch, started_at, completed_at, config)
- `findings` — All findings conforming to the schema in `finding-format.md`
- `triage_log` — Status change history (who changed what, when, and why)
**Rules:**
- All mutations happen here first. Never edit `findings.json` or `report.md` directly.
- The database is append-only during a scan. Findings are inserted, never deleted (status changes use the `status` field).
- Triage actions (marking false-positive, accepted-risk, etc.) are recorded with a timestamp and reason in `triage_log`.
**Typical operations:**
```sql
-- Count open findings by severity
SELECT severity, COUNT(*) FROM findings
WHERE scan_id = ? AND status = 'open'
GROUP BY severity ORDER BY
CASE severity
WHEN 'critical' THEN 1
WHEN 'high' THEN 2
WHEN 'medium' THEN 3
WHEN 'low' THEN 4
WHEN 'info' THEN 5
END;
```
---
### findings.json — Sealed Export
A JSON array of all findings from the scan, exported from `scan.db` at finalization time.
**Properties:**
- Generated by `finalize.py` — never written by hand
- Represents a point-in-time snapshot of findings at scan completion
- Immutable after generation. If findings change (triage, fixes), re-run finalization to produce a new export
- Each entry conforms exactly to the schema in `finding-format.md`
**Structure:**
```json
{
"scan_id": "f0e1d2c3-b4a5-6789-0123-456789abcdef",
"repository": "myorg/myapp",
"branch": "main",
"finalized_at": "2026-06-24T03:30:00Z",
"findings": [
{ /* finding object per finding-format.md */ }
]
}
```
---
### report.md — Human-Readable Report
The markdown report formatted according to `report-format.md`. Intended for human review, pull request comments, or export to documentation systems.
**Properties:**
- Generated from the same data as `findings.json` at finalization time
- Read-only artifact — regenerate rather than edit
- Self-contained: readers should not need to consult `scan.db` or `findings.json`
---
### integrity.sha256 — Tamper Detection
A SHA-256 hash of `findings.json`, computed at finalization time.
**Format:**
```
<hex-encoded sha256> findings.json
```
Example:
```
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 findings.json
```
**Purpose:**
- Allows downstream tools (CI gates, compliance checks, dashboards) to verify that `findings.json` has not been modified since finalization
- If the hash does not match, the findings export must be considered untrusted and regenerated from `scan.db`
**Verification:**
```bash
cd .security/
sha256sum -c integrity.sha256
```
Expected output on success: `findings.json: OK`
---
### threat-model.md — Repository Threat Model (Optional)
A structured threat model for the repository, generated on first scan or when explicitly requested. Not regenerated on every scan.
**Contains:**
- Trust boundaries (what's inside vs. outside the security perimeter)
- Data flows (what sensitive data moves where)
- Entry points (APIs, file uploads, webhooks, CLI inputs)
- Assets (databases, credentials, user data, secrets)
- Threat actors (who might attack and what they'd target)
**Rules:**
- Only created when explicitly triggered or on first scan of a new repository
- Updated manually or on request — not overwritten by routine scans
- Informs severity decisions: a finding that crosses a trust boundary is more severe than one contained within a trusted zone
---
## Lifecycle
1. **Scan starts**`scan.db` is created (or a new scan row is inserted into an existing database)
2. **Analysis runs** → Findings are inserted into `scan.db` as they are discovered
3. **Triage (optional)** → Agent or human reviews findings, updates statuses in `scan.db`
4. **Finalization**`finalize.py` exports `findings.json`, generates `report.md`, computes `integrity.sha256`
5. **Post-seal** → Artifacts are committed, pushed, or attached to a PR. No further modifications without re-finalization.
---
## Gitignore Considerations
The `.security/` directory should generally be committed so findings are tracked alongside code. However:
- `scan.db` may be gitignored in repositories where only the sealed artifacts matter (reduces churn from SQLite binary diffs)
- If `scan.db` is gitignored, `findings.json` + `integrity.sha256` become the durable record
Recommended `.gitignore` entry when excluding the database:
```gitignore
.security/scan.db
```

View File

@@ -0,0 +1,162 @@
# Severity Policy
Practical decision criteria for assigning severity to security findings. Apply this policy consistently — do not assign severity based on gut feeling.
---
## Severity Levels
### Critical
The vulnerability allows an attacker to fully compromise the system, its data, or its users with minimal effort and no special access.
**Assign critical when:**
- Remote code execution (RCE) is achievable
- Authentication can be bypassed entirely, granting full access
- PII, credentials, or payment data is directly exposed or exfiltrable
- Supply chain compromise: malicious dependency, tampered build artifact, or poisoned CI pipeline
- Pre-authentication exploitation — no account or privileges required
**Examples:**
- Unauthenticated endpoint that returns all user records with passwords
- Deserialization vulnerability allowing arbitrary command execution
- Hardcoded production credentials (database, payment processor, admin tokens)
- Dependency with an actively exploited RCE CVE
---
### High
The vulnerability enables significant damage but requires slightly more effort or minimal access (low-privilege account).
**Assign high when:**
- SQL injection or XSS that enables session hijacking or credential theft
- Privilege escalation from normal user to admin
- SSRF that reaches internal services, metadata endpoints, or private networks
- Significant data exposure (not full breach, but sensitive records accessible)
- Authentication flaws that weaken but don't fully bypass access control
- File upload allowing server-side execution
**Examples:**
- Stored XSS in a comment field that steals admin session cookies
- IDOR allowing any authenticated user to read other users' private data
- SSRF reaching cloud metadata endpoint (`169.254.169.254`)
- JWT signature not verified, allowing role escalation
---
### Medium
The vulnerability has real security impact but is limited in scope, requires chaining, or affects non-critical paths.
**Assign medium when:**
- Stored XSS that cannot access session tokens (HttpOnly cookies in place)
- Information disclosure: stack traces, internal file paths, software versions
- Missing security headers (CSP, X-Frame-Options) on sensitive pages
- Weak cryptography in non-critical paths (e.g., MD5 for non-password hashing)
- CSRF on state-changing but non-critical actions
- Open redirect usable for phishing
**Examples:**
- Error page leaks full stack trace including internal IP addresses
- No CSP header on pages that render user-generated content
- Password reset token generated with insufficient entropy (but short-lived)
- CSRF on profile display name change (not on password/email change)
---
### Low
The issue has minimal direct security impact but represents a gap in defense-in-depth or hygiene.
**Assign low when:**
- Verbose error messages revealing framework version or minor internals
- Missing rate limiting on non-critical endpoints
- Minor misconfigurations with no direct exploit path
- Dependencies with CVEs that have no practical exploit in this context
- Cookie without `Secure` flag in a development-only path
- Directory listing enabled but exposing only public assets
**Examples:**
- Server responds with `X-Powered-By: Express` header
- No rate limit on the "forgot password" endpoint (but tokens are single-use and short-lived)
- Dependency has a CVE for a function the project never calls
- CORS allows `*` on a public read-only API with no auth
---
### Info
Not a vulnerability. An observation, best-practice recommendation, or note for future hardening.
**Assign info when:**
- Best practice not followed but no exploitable condition exists
- Code quality issue with security implications (e.g., error handling inconsistency)
- Suggestion for future improvement (e.g., "consider adding Subresource Integrity")
- Informational notes about architecture or trust boundaries
**Examples:**
- Recommend enabling HSTS preload (HSTS is already present, just not preloaded)
- Suggest adding `integrity` attributes to CDN script tags
- Note that logging does not capture failed authentication attempts
---
## Dynamic Baseline
Severity não é absoluta — é relativa ao que a aplicação é e ao que comparáveis aceitam.
### Como Calibrar
1. **Identifique o comparável** em Phase 1 (CMS → outros CMSes, API gateway → outros API gateways, novel app → sem comparável)
2. **Verifique se o pattern existe no comparável** — se sim e foi explorado, é finding MAIS FORTE. Se nunca explorado em anos de produção, entenda por quê.
3. **Ajuste severity pela distância do padrão aceito** — se TODO app nessa categoria tem o mesmo pattern e ninguém considera vulnerability, não reporte como HIGH.
4. **Não use baseline para DESCARTAR** — use para calibrar. Um pattern perigoso é perigoso mesmo se o comparável também o tem.
### Distinction: HIGH vs MEDIUM para Business Logic
- **HIGH**: O finding derrota um security boundary explícito. User performa ação que o sistema explicitamente gate atrás de higher role, e a ação tem consequências reais.
- **MEDIUM**: Bypass com consequências reais mas limitadas. Requer auth, impacto confinado a dados do atacante, ou conditions uncommon.
---
## Don't Overcall
Common mistakes that inflate severity beyond what the evidence supports:
| Mistake | Why it's wrong | Correct severity |
|---------|---------------|-----------------|
| Reflected XSS behind authentication marked as critical | Requires social engineering of an already-authenticated user; session cookies are HttpOnly | Medium (or High if cookies are accessible) |
| Missing HSTS marked as critical | HSTS absence alone doesn't enable exploitation; it's defense-in-depth | Low (or Medium if the site handles sensitive auth flows over HTTP) |
| Dependency CVE with no reachable code path marked as high | If the vulnerable function is never called, there's no exploit | Low or Info |
| Missing rate limiting on login marked as high | Only matters if there's no account lockout, no CAPTCHA, and passwords are weak | Low (escalate to Medium if no compensating controls exist) |
| Information disclosure of software version marked as high | Version numbers alone don't enable attack; they help an attacker enumerate but require a corresponding vulnerability | Low |
| Self-XSS (user can only attack themselves) marked as medium | No impact on other users; no realistic attack scenario | Info |
| CORS misconfiguration on a public API with no auth | If the API is intentionally public and has no user context, CORS is irrelevant | Info |
| **Multiple dependency CVEs listed at face value without project context** | If 9 CVEs are listed but only 1 is exploitable due to missing preconditions, reporting "9 CRITICAL CVEs" is misleading and erodes trust | Analyze each individually, assign per-CVE real severity |
**The rule:** Severity reflects *demonstrated impact*, not *theoretical worst case*. If you can't articulate the realistic attack scenario and its consequences in 2 sentences, you're probably overcalling.
### CVE Cross-Reference Protocol (Mandatory)
Before assigning severity to any dependency CVE:
1. **Read the advisory** — identify the exact precondition (which function, which feature, which config)
2. **Grep the codebase** — does the project use that function/feature? Cite the evidence (file:line or "0 results")
3. **Check the environment** — does prod have the infrastructure the CVE requires? (CDN, multi-user, Windows, etc.)
4. **DAST validate** — did the probe confirm exploitability in localhost? In production?
5. **Assign real severity** — based on what you proved, not what the advisory says generically
A bulk "upgrade all deps" recommendation is fine. But the *severity* must reflect this project, not all projects.
---
## Severity Decision Flowchart
1. **Can an unauthenticated attacker achieve RCE, full data breach, or complete auth bypass?** → Critical
2. **Can a low-privilege attacker steal sessions, escalate privileges, or access significant sensitive data?** → High
3. **Is there real but limited impact (scoped data leak, partial XSS, missing hardening on sensitive pages)?** → Medium
4. **Is it a hygiene gap with no direct exploit path in this context?** → Low
5. **Is it purely advisory with no current exploitability?** → Info
When in doubt between two levels, ask: "Can I demonstrate concrete harm to a user or the system?" If yes, go with the higher level. If not, go lower.