jaku.sh 1.0.1 → 1.0.2
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 +20 -15
- package/action.yml +1 -1
- package/package.json +2 -2
- package/src/agents/orchestrator.js +1 -1
- package/src/cli.js +33 -2
- package/src/reporting/compliance-reporter.js +317 -0
- package/src/reporting/report-generator.js +5 -1
- package/src/reporting/sarif-generator.js +2 -2
- package/src/utils/finding.js +6 -1
- package/src/utils/owasp-mapper.js +251 -0
package/README.md
CHANGED
|
@@ -28,27 +28,23 @@ JAKU crawls your entire app, generates test cases, probes for security vulnerabi
|
|
|
28
28
|
## Quick Start
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
cd jaku
|
|
34
|
-
npm install
|
|
35
|
-
npx playwright install chromium
|
|
36
|
-
|
|
37
|
-
# Option B: Install globally via npm
|
|
38
|
-
npm install -g @theshantanupandey/jaku
|
|
31
|
+
# Install globally via npm
|
|
32
|
+
npm install -g jaku.sh
|
|
39
33
|
npx playwright install chromium
|
|
40
34
|
|
|
41
35
|
# Run a full scan (QA + Security + AI + Logic + API)
|
|
42
|
-
jaku scan https://your-app.dev --
|
|
43
|
-
# or without global install:
|
|
44
|
-
node src/cli.js scan https://your-app.dev --verbose
|
|
36
|
+
jaku scan https://your-app.dev --prod-safe
|
|
45
37
|
|
|
46
|
-
#
|
|
47
|
-
jaku
|
|
38
|
+
# Quick scan (10 pages, fast feedback)
|
|
39
|
+
jaku scan https://your-app.dev --profile quick --prod-safe
|
|
48
40
|
|
|
41
|
+
# With OWASP Top 10 compliance report
|
|
42
|
+
jaku scan https://your-app.dev --compliance owasp --prod-safe
|
|
43
|
+
|
|
44
|
+
# AI abuse testing only
|
|
45
|
+
jaku ai https://your-ai-app.dev
|
|
49
46
|
|
|
50
47
|
# Reports are saved to ./jaku-reports/<timestamp>/
|
|
51
|
-
# latest-report.json is auto-updated at project root after each scan
|
|
52
48
|
```
|
|
53
49
|
|
|
54
50
|
### First Scan Walkthrough
|
|
@@ -414,6 +410,8 @@ Correlations appear in the CLI output and reports with severity escalation.
|
|
|
414
410
|
| `-c, --config <path>` | Path to config file | `./jaku.config.json` |
|
|
415
411
|
| `-o, --output <dir>` | Output directory for reports | `./jaku-reports/<timestamp>` |
|
|
416
412
|
| `-s, --severity <level>` | Minimum severity threshold (`critical`, `high`, `medium`, `low`) | `low` |
|
|
413
|
+
| `--profile <type>` | Scan profile: `quick`, `deep`, `ci` | — |
|
|
414
|
+
| `--compliance <framework>` | Generate compliance report (`owasp`) | — |
|
|
417
415
|
| `--max-pages <n>` | Maximum pages to crawl | `50` |
|
|
418
416
|
| `--max-depth <n>` | Maximum crawl depth | `5` |
|
|
419
417
|
| `--halt-on-critical` | Abort scan immediately on any critical finding | off |
|
|
@@ -434,6 +432,7 @@ Every scan generates 5 report files:
|
|
|
434
432
|
| **HTML** | `report.html` | Self-contained browsable report with severity charts |
|
|
435
433
|
| **SARIF** | `report.sarif` | GitHub/GitLab Security Dashboard integration (SARIF v2.1.0) |
|
|
436
434
|
| **Diff** | `diff-report.md` | Regression detection vs. previous scan run |
|
|
435
|
+
| **OWASP Compliance** | `compliance-owasp.*` | OWASP Top 10 pass/fail report (JSON + MD + HTML) — requires `--compliance owasp` |
|
|
437
436
|
|
|
438
437
|
### Examples
|
|
439
438
|
|
|
@@ -468,7 +467,7 @@ node src/cli.js ai https://myapp.dev/api/chat --max-pages 1 -v
|
|
|
468
467
|
```
|
|
469
468
|
╦╔═╗╦╔═╦ ╦
|
|
470
469
|
║╠═╣╠╩╗║ ║ 呪 Autonomous Security & Quality Intelligence
|
|
471
|
-
╚╝╩ ╩╩ ╩╚═╝ v1.0.
|
|
470
|
+
╚╝╩ ╩╩ ╩╚═╝ v1.0.2 · Multi-Agent
|
|
472
471
|
|
|
473
472
|
Target: https://your-app.dev
|
|
474
473
|
Modules: QA + SECURITY + AI
|
|
@@ -634,3 +633,9 @@ Every JAKU scan generates a self-contained **HTML report** at `jaku-reports/<tim
|
|
|
634
633
|
## License
|
|
635
634
|
|
|
636
635
|
[Jaku Public License v1.0](./LICENSE) — free to use, modify, and distribute with attribution. See [LICENSE](./LICENSE) for full terms.
|
|
636
|
+
|
|
637
|
+
---
|
|
638
|
+
|
|
639
|
+
**Website:** [jaku.app](https://jaku.app)
|
|
640
|
+
**npm:** [jaku.sh](https://www.npmjs.com/package/jaku.sh)
|
|
641
|
+
**GitHub:** [theshantanupandey/jaku](https://github.com/theshantanupandey/jaku)
|
package/action.yml
CHANGED
|
@@ -217,7 +217,7 @@ runs:
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
body += '\n---\n*Scanned by [JAKU](https://github.com/jaku-security/jaku) v1.0.
|
|
220
|
+
body += '\n---\n*Scanned by [JAKU](https://github.com/jaku-security/jaku) v1.0.2*';
|
|
221
221
|
} else {
|
|
222
222
|
body += '⚠️ Scan completed but no report was generated. Check workflow logs for errors.';
|
|
223
223
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jaku.sh",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "JAKU (呪) — Autonomous Security & Quality Intelligence Agent for vibe-coded apps. XSS, SQLi, prompt injection, QA testing, and attack chain correlation in one command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/cli.js",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"url": "https://github.com/theshantanupandey"
|
|
43
43
|
},
|
|
44
44
|
"license": "SEE LICENSE IN LICENSE",
|
|
45
|
-
"homepage": "https://
|
|
45
|
+
"homepage": "https://jaku.app",
|
|
46
46
|
"repository": {
|
|
47
47
|
"type": "git",
|
|
48
48
|
"url": "https://github.com/theshantanupandey/jaku.git"
|
package/src/cli.js
CHANGED
|
@@ -12,12 +12,13 @@ import { AIAgent } from './agents/ai-agent.js';
|
|
|
12
12
|
import { LogicAgent } from './agents/logic-agent.js';
|
|
13
13
|
import { APIAgent } from './agents/api-agent.js';
|
|
14
14
|
import { ReportGenerator } from './reporting/report-generator.js';
|
|
15
|
+
import { ComplianceReporter } from './reporting/compliance-reporter.js';
|
|
15
16
|
import { AuthManager } from './core/auth-manager.js';
|
|
16
17
|
|
|
17
18
|
const BANNER = `
|
|
18
19
|
${chalk.hex('#00ff88').bold(' ╦╔═╗╦╔═╦ ╦')}
|
|
19
20
|
${chalk.hex('#00ff88').bold(' ║╠═╣╠╩╗║ ║')} ${chalk.dim('呪 Autonomous Security & Quality Intelligence')}
|
|
20
|
-
${chalk.hex('#00ff88').bold(' ╚╝╩ ╩╩ ╩╚═╝')} ${chalk.dim('v1.0.
|
|
21
|
+
${chalk.hex('#00ff88').bold(' ╚╝╩ ╩╩ ╩╚═╝')} ${chalk.dim('v1.0.2 · Multi-Agent')}
|
|
21
22
|
`;
|
|
22
23
|
|
|
23
24
|
const program = new Command();
|
|
@@ -25,7 +26,7 @@ const program = new Command();
|
|
|
25
26
|
program
|
|
26
27
|
.name('jaku')
|
|
27
28
|
.description('JAKU (呪) — Autonomous QA & Security scanning agent for vibe-coded apps')
|
|
28
|
-
.version('1.0.
|
|
29
|
+
.version('1.0.2');
|
|
29
30
|
|
|
30
31
|
// ═══════════════════════════════════════════════
|
|
31
32
|
// Multi-Agent Scan Runner
|
|
@@ -225,6 +226,18 @@ async function runScan(url, options, modulesToRun) {
|
|
|
225
226
|
outputDir: config.output_dir,
|
|
226
227
|
});
|
|
227
228
|
|
|
229
|
+
// Generate compliance report if requested
|
|
230
|
+
let complianceResult = null;
|
|
231
|
+
if (options.compliance) {
|
|
232
|
+
const complianceReporter = new ComplianceReporter(config, logger);
|
|
233
|
+
complianceResult = complianceReporter.generate(
|
|
234
|
+
options.compliance,
|
|
235
|
+
results.findings,
|
|
236
|
+
reportDir,
|
|
237
|
+
{ target: url, version: '1.0.2', scannedAt: new Date().toISOString() }
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
228
241
|
reportSpinner.succeed(`Reports saved to ${chalk.underline(reportDir)}`);
|
|
229
242
|
|
|
230
243
|
// ═══════════════════════════════════════
|
|
@@ -266,6 +279,23 @@ async function runScan(url, options, modulesToRun) {
|
|
|
266
279
|
}
|
|
267
280
|
}
|
|
268
281
|
|
|
282
|
+
// OWASP Compliance Summary
|
|
283
|
+
if (complianceResult?.compliance) {
|
|
284
|
+
const c = complianceResult.compliance;
|
|
285
|
+
console.log();
|
|
286
|
+
console.log(chalk.hex('#00ff88').bold(' ═══ OWASP TOP 10 COMPLIANCE ═══'));
|
|
287
|
+
console.log();
|
|
288
|
+
const scoreColor = c.percentage >= 80 ? '#00e676' : c.percentage >= 50 ? '#ffd600' : '#ff1744';
|
|
289
|
+
console.log(` ${chalk.dim('Score:')} ${chalk.hex(scoreColor).bold(`${c.score}/${c.total}`)} (${c.percentage}%)`);
|
|
290
|
+
console.log();
|
|
291
|
+
for (const cat of c.categories) {
|
|
292
|
+
const icon = cat.status === 'pass' ? chalk.hex('#00e676')('✔') : cat.status === 'fail' ? chalk.red('✘') : chalk.yellow('⚠');
|
|
293
|
+
const label = `${cat.id} ${cat.name}`;
|
|
294
|
+
const count = cat.findingsCount > 0 ? chalk.dim(` (${cat.findingsCount} findings)`) : '';
|
|
295
|
+
console.log(` ${icon} ${label}${count}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
269
299
|
console.log();
|
|
270
300
|
|
|
271
301
|
if (summary.critical > 0) {
|
|
@@ -298,6 +328,7 @@ program
|
|
|
298
328
|
.option('-m, --modules <list>', 'Comma-separated modules to run (qa,security,ai,logic,api)', 'qa,security,ai,logic,api')
|
|
299
329
|
.option('-s, --severity <level>', 'Minimum severity threshold (critical|high|medium|low)', 'low')
|
|
300
330
|
.option('--profile <type>', 'Scan profile: quick|deep|ci (overrides crawl settings)')
|
|
331
|
+
.option('--compliance <framework>', 'Generate compliance report (owasp)')
|
|
301
332
|
.option('--json', 'Output JSON report')
|
|
302
333
|
.option('--html', 'Output HTML report')
|
|
303
334
|
.option('--max-pages <n>', 'Maximum pages to crawl', '50')
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getComplianceStatus } from '../utils/owasp-mapper.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ComplianceReporter — Generates OWASP Top 10 compliance reports
|
|
7
|
+
* in JSON, Markdown, and HTML formats.
|
|
8
|
+
*/
|
|
9
|
+
export class ComplianceReporter {
|
|
10
|
+
constructor(config, logger) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate compliance reports for the given framework.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} framework - 'owasp' (extensible for future frameworks)
|
|
19
|
+
* @param {Array} findings - All findings (should have `owasp` field)
|
|
20
|
+
* @param {string} reportDir - Output directory
|
|
21
|
+
* @param {object} meta - Report metadata (target, version, scannedAt)
|
|
22
|
+
*/
|
|
23
|
+
generate(framework, findings, reportDir, meta) {
|
|
24
|
+
if (framework !== 'owasp') {
|
|
25
|
+
this.logger?.warn?.(`Unknown compliance framework: ${framework}`);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const compliance = getComplianceStatus(findings);
|
|
30
|
+
compliance.meta = meta;
|
|
31
|
+
|
|
32
|
+
// JSON
|
|
33
|
+
const jsonPath = path.join(reportDir, 'compliance-owasp.json');
|
|
34
|
+
fs.writeFileSync(jsonPath, JSON.stringify(compliance, null, 2), 'utf-8');
|
|
35
|
+
|
|
36
|
+
// Markdown
|
|
37
|
+
const mdPath = path.join(reportDir, 'compliance-owasp.md');
|
|
38
|
+
fs.writeFileSync(mdPath, this._generateMarkdown(compliance), 'utf-8');
|
|
39
|
+
|
|
40
|
+
// HTML
|
|
41
|
+
const htmlPath = path.join(reportDir, 'compliance-owasp.html');
|
|
42
|
+
fs.writeFileSync(htmlPath, this._generateHTML(compliance), 'utf-8');
|
|
43
|
+
|
|
44
|
+
this.logger?.info?.(`Compliance reports generated: ${jsonPath}`);
|
|
45
|
+
return { jsonPath, mdPath, htmlPath, compliance };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ═══════════════════════════════════════════════
|
|
49
|
+
// Markdown Report
|
|
50
|
+
// ═══════════════════════════════════════════════
|
|
51
|
+
|
|
52
|
+
_generateMarkdown(compliance) {
|
|
53
|
+
const { meta } = compliance;
|
|
54
|
+
let md = '';
|
|
55
|
+
|
|
56
|
+
md += `# 呪 JAKU — OWASP Top 10 (2021) Compliance Report\n\n`;
|
|
57
|
+
md += `**Target:** ${meta.target} \n`;
|
|
58
|
+
md += `**Scanned:** ${meta.scannedAt} \n`;
|
|
59
|
+
md += `**Agent Version:** ${meta.version} \n\n`;
|
|
60
|
+
|
|
61
|
+
md += `---\n\n`;
|
|
62
|
+
md += `## Compliance Score: ${compliance.score}/${compliance.total} (${compliance.percentage}%)\n\n`;
|
|
63
|
+
|
|
64
|
+
// Summary table
|
|
65
|
+
md += `| Category | Status | Findings | Critical | High | Medium | Low |\n`;
|
|
66
|
+
md += `|----------|--------|----------|----------|------|--------|-----|\n`;
|
|
67
|
+
|
|
68
|
+
for (const cat of compliance.categories) {
|
|
69
|
+
const statusIcon = cat.status === 'pass' ? '✅ PASS' : cat.status === 'fail' ? '❌ FAIL' : '⚠️ WARN';
|
|
70
|
+
md += `| ${cat.id} ${cat.name} | ${statusIcon} | ${cat.findingsCount} | ${cat.criticalCount} | ${cat.highCount} | ${cat.mediumCount} | ${cat.lowCount} |\n`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
md += `\n---\n\n`;
|
|
74
|
+
|
|
75
|
+
// Detailed findings per category
|
|
76
|
+
for (const cat of compliance.categories) {
|
|
77
|
+
if (cat.findingsCount === 0) continue;
|
|
78
|
+
|
|
79
|
+
md += `## ${cat.id}: ${cat.name}\n\n`;
|
|
80
|
+
md += `> ${cat.description}\n\n`;
|
|
81
|
+
md += `**Status:** ${cat.status === 'fail' ? '❌ FAIL' : '⚠️ WARN'} — ${cat.findingsCount} finding(s)\n\n`;
|
|
82
|
+
|
|
83
|
+
for (const f of cat.findings) {
|
|
84
|
+
const sevIcon = { critical: '🔴', high: '🟠', medium: '🟡', low: '🔵', info: '⚪' }[f.severity] || '⚪';
|
|
85
|
+
md += `### ${sevIcon} ${f.id}: ${f.title}\n\n`;
|
|
86
|
+
md += `**Severity:** ${f.severity.toUpperCase()} \n`;
|
|
87
|
+
md += `**Affected:** ${f.affected_surface} \n\n`;
|
|
88
|
+
md += `${f.description}\n\n`;
|
|
89
|
+
if (f.remediation) {
|
|
90
|
+
md += `**Remediation:** ${f.remediation}\n\n`;
|
|
91
|
+
}
|
|
92
|
+
md += `---\n\n`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
md += `\n*Compliance report generated by JAKU 呪 v${meta.version}*\n`;
|
|
97
|
+
return md;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ═══════════════════════════════════════════════
|
|
101
|
+
// HTML Report
|
|
102
|
+
// ═══════════════════════════════════════════════
|
|
103
|
+
|
|
104
|
+
_generateHTML(compliance) {
|
|
105
|
+
const { meta } = compliance;
|
|
106
|
+
const passPercent = compliance.percentage;
|
|
107
|
+
const scoreColor = passPercent >= 80 ? '#00e676' : passPercent >= 50 ? '#ffd600' : '#ff1744';
|
|
108
|
+
|
|
109
|
+
const categoriesHTML = compliance.categories.map(cat => {
|
|
110
|
+
const statusClass = cat.status === 'pass' ? 'pass' : cat.status === 'fail' ? 'fail' : 'warn';
|
|
111
|
+
const statusIcon = cat.status === 'pass' ? '✅' : cat.status === 'fail' ? '❌' : '⚠️';
|
|
112
|
+
const findingsHTML = cat.findings.map(f => `
|
|
113
|
+
<div class="finding-item">
|
|
114
|
+
<span class="sev-dot ${f.severity}"></span>
|
|
115
|
+
<span class="finding-id">${this._escapeHtml(f.id)}</span>
|
|
116
|
+
<span class="finding-title-text">${this._escapeHtml(f.title)}</span>
|
|
117
|
+
<span class="sev-badge ${f.severity}">${f.severity}</span>
|
|
118
|
+
</div>`).join('');
|
|
119
|
+
|
|
120
|
+
return `
|
|
121
|
+
<div class="category-card ${statusClass}">
|
|
122
|
+
<div class="category-header">
|
|
123
|
+
<div class="category-id">${cat.id}</div>
|
|
124
|
+
<div class="category-name">${this._escapeHtml(cat.name)}</div>
|
|
125
|
+
<div class="category-status ${statusClass}">${statusIcon} ${cat.status.toUpperCase()}</div>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="category-desc">${this._escapeHtml(cat.description)}</div>
|
|
128
|
+
<div class="category-stats">
|
|
129
|
+
<span class="stat">${cat.findingsCount} findings</span>
|
|
130
|
+
${cat.criticalCount > 0 ? `<span class="stat critical">${cat.criticalCount} critical</span>` : ''}
|
|
131
|
+
${cat.highCount > 0 ? `<span class="stat high">${cat.highCount} high</span>` : ''}
|
|
132
|
+
${cat.mediumCount > 0 ? `<span class="stat medium">${cat.mediumCount} medium</span>` : ''}
|
|
133
|
+
${cat.lowCount > 0 ? `<span class="stat low">${cat.lowCount} low</span>` : ''}
|
|
134
|
+
</div>
|
|
135
|
+
${cat.findingsCount > 0 ? `<details class="category-findings"><summary>View Findings</summary>${findingsHTML}</details>` : ''}
|
|
136
|
+
</div>`;
|
|
137
|
+
}).join('');
|
|
138
|
+
|
|
139
|
+
return `<!DOCTYPE html>
|
|
140
|
+
<html lang="en" data-theme="dark">
|
|
141
|
+
<head>
|
|
142
|
+
<meta charset="UTF-8">
|
|
143
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
144
|
+
<title>JAKU OWASP Compliance — ${meta.target}</title>
|
|
145
|
+
<style>
|
|
146
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
147
|
+
:root {
|
|
148
|
+
--bg: #0a0a0f; --surface: #12121a; --surface-2: #1a1a25;
|
|
149
|
+
--text: #e0e0e8; --text-dim: #8888a0; --accent: #00ff88;
|
|
150
|
+
--pass: #00e676; --fail: #ff1744; --warn: #ffd600;
|
|
151
|
+
--critical: #ff1744; --high: #ff6d00; --medium: #ffd600;
|
|
152
|
+
--low: #2979ff; --info: #90a4ae; --border: #2a2a3a;
|
|
153
|
+
}
|
|
154
|
+
body {
|
|
155
|
+
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
|
156
|
+
background: var(--bg); color: var(--text); line-height: 1.6;
|
|
157
|
+
padding: 2rem; max-width: 1200px; margin: 0 auto;
|
|
158
|
+
}
|
|
159
|
+
h1 { font-size: 1.8rem; color: var(--accent); margin-bottom: 0.5rem; }
|
|
160
|
+
.meta { color: var(--text-dim); font-size: 0.85rem; margin-bottom: 2rem; }
|
|
161
|
+
.meta span { margin-right: 2rem; }
|
|
162
|
+
|
|
163
|
+
/* Score Ring */
|
|
164
|
+
.score-container {
|
|
165
|
+
display: flex; align-items: center; gap: 2rem;
|
|
166
|
+
background: var(--surface); border: 1px solid var(--border);
|
|
167
|
+
border-radius: 12px; padding: 2rem; margin: 2rem 0;
|
|
168
|
+
}
|
|
169
|
+
.score-ring {
|
|
170
|
+
position: relative; width: 140px; height: 140px; flex-shrink: 0;
|
|
171
|
+
}
|
|
172
|
+
.score-ring svg { transform: rotate(-90deg); }
|
|
173
|
+
.score-ring .bg { stroke: var(--surface-2); }
|
|
174
|
+
.score-ring .fg { stroke: ${scoreColor}; stroke-linecap: round; transition: stroke-dashoffset 1s ease; }
|
|
175
|
+
.score-value {
|
|
176
|
+
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
|
177
|
+
font-size: 2rem; font-weight: bold; color: ${scoreColor};
|
|
178
|
+
}
|
|
179
|
+
.score-label { font-size: 0.85rem; color: var(--text-dim); }
|
|
180
|
+
.score-details h2 { font-size: 1.4rem; margin-bottom: 0.5rem; }
|
|
181
|
+
.score-breakdown { display: flex; gap: 1.5rem; margin-top: 0.75rem; }
|
|
182
|
+
.score-breakdown .item { text-align: center; }
|
|
183
|
+
.score-breakdown .item .num { font-size: 1.5rem; font-weight: bold; }
|
|
184
|
+
.score-breakdown .item .lbl { font-size: 0.7rem; color: var(--text-dim); text-transform: uppercase; }
|
|
185
|
+
.score-breakdown .item.pass .num { color: var(--pass); }
|
|
186
|
+
.score-breakdown .item.fail .num { color: var(--fail); }
|
|
187
|
+
.score-breakdown .item.warn .num { color: var(--warn); }
|
|
188
|
+
|
|
189
|
+
/* Category Cards */
|
|
190
|
+
.category-card {
|
|
191
|
+
background: var(--surface); border: 1px solid var(--border);
|
|
192
|
+
border-radius: 8px; padding: 1.25rem; margin: 1rem 0;
|
|
193
|
+
border-left: 4px solid var(--border);
|
|
194
|
+
}
|
|
195
|
+
.category-card.pass { border-left-color: var(--pass); }
|
|
196
|
+
.category-card.fail { border-left-color: var(--fail); }
|
|
197
|
+
.category-card.warn { border-left-color: var(--warn); }
|
|
198
|
+
.category-header {
|
|
199
|
+
display: flex; align-items: center; gap: 1rem; margin-bottom: 0.5rem;
|
|
200
|
+
}
|
|
201
|
+
.category-id {
|
|
202
|
+
font-size: 0.75rem; color: var(--accent); font-weight: bold;
|
|
203
|
+
background: var(--surface-2); padding: 2px 8px; border-radius: 4px;
|
|
204
|
+
}
|
|
205
|
+
.category-name { font-weight: bold; font-size: 0.95rem; flex: 1; }
|
|
206
|
+
.category-status { font-size: 0.8rem; font-weight: bold; }
|
|
207
|
+
.category-status.pass { color: var(--pass); }
|
|
208
|
+
.category-status.fail { color: var(--fail); }
|
|
209
|
+
.category-status.warn { color: var(--warn); }
|
|
210
|
+
.category-desc { font-size: 0.8rem; color: var(--text-dim); margin: 0.5rem 0; }
|
|
211
|
+
.category-stats { display: flex; gap: 1rem; margin-top: 0.5rem; }
|
|
212
|
+
.stat {
|
|
213
|
+
font-size: 0.75rem; padding: 2px 8px; border-radius: 4px;
|
|
214
|
+
background: var(--surface-2); color: var(--text-dim);
|
|
215
|
+
}
|
|
216
|
+
.stat.critical { color: var(--critical); }
|
|
217
|
+
.stat.high { color: var(--high); }
|
|
218
|
+
.stat.medium { color: var(--medium); }
|
|
219
|
+
.stat.low { color: var(--low); }
|
|
220
|
+
|
|
221
|
+
/* Finding items inside category */
|
|
222
|
+
.category-findings { margin-top: 0.75rem; }
|
|
223
|
+
.category-findings summary {
|
|
224
|
+
cursor: pointer; font-size: 0.8rem; color: var(--accent);
|
|
225
|
+
}
|
|
226
|
+
.finding-item {
|
|
227
|
+
display: flex; align-items: center; gap: 0.5rem;
|
|
228
|
+
padding: 0.5rem 0; border-bottom: 1px solid var(--border);
|
|
229
|
+
font-size: 0.8rem;
|
|
230
|
+
}
|
|
231
|
+
.finding-item:last-child { border-bottom: none; }
|
|
232
|
+
.sev-dot {
|
|
233
|
+
width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
|
|
234
|
+
}
|
|
235
|
+
.sev-dot.critical { background: var(--critical); }
|
|
236
|
+
.sev-dot.high { background: var(--high); }
|
|
237
|
+
.sev-dot.medium { background: var(--medium); }
|
|
238
|
+
.sev-dot.low { background: var(--low); }
|
|
239
|
+
.sev-dot.info { background: var(--info); }
|
|
240
|
+
.finding-id { color: var(--text-dim); font-size: 0.7rem; min-width: 110px; }
|
|
241
|
+
.finding-title-text { flex: 1; }
|
|
242
|
+
.sev-badge {
|
|
243
|
+
display: inline-block; padding: 1px 6px; border-radius: 3px;
|
|
244
|
+
font-size: 0.65rem; font-weight: bold; text-transform: uppercase;
|
|
245
|
+
}
|
|
246
|
+
.sev-badge.critical { background: var(--critical); color: #fff; }
|
|
247
|
+
.sev-badge.high { background: var(--high); color: #000; }
|
|
248
|
+
.sev-badge.medium { background: var(--medium); color: #000; }
|
|
249
|
+
.sev-badge.low { background: var(--low); color: #fff; }
|
|
250
|
+
.sev-badge.info { background: var(--info); color: #000; }
|
|
251
|
+
|
|
252
|
+
footer {
|
|
253
|
+
margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--border);
|
|
254
|
+
color: var(--text-dim); font-size: 0.75rem; text-align: center;
|
|
255
|
+
}
|
|
256
|
+
</style>
|
|
257
|
+
</head>
|
|
258
|
+
<body>
|
|
259
|
+
<h1>呪 JAKU — OWASP Top 10 Compliance</h1>
|
|
260
|
+
<div class="meta">
|
|
261
|
+
<span>Target: ${meta.target}</span>
|
|
262
|
+
<span>Scanned: ${new Date(meta.scannedAt).toLocaleString()}</span>
|
|
263
|
+
<span>Framework: OWASP Top 10 (2021)</span>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<div class="score-container">
|
|
267
|
+
<div class="score-ring">
|
|
268
|
+
<svg viewBox="0 0 140 140" width="140" height="140">
|
|
269
|
+
<circle class="bg" cx="70" cy="70" r="60" fill="none" stroke-width="10"/>
|
|
270
|
+
<circle class="fg" cx="70" cy="70" r="60" fill="none" stroke-width="10"
|
|
271
|
+
stroke-dasharray="${2 * Math.PI * 60}"
|
|
272
|
+
stroke-dashoffset="${2 * Math.PI * 60 * (1 - passPercent / 100)}"/>
|
|
273
|
+
</svg>
|
|
274
|
+
<div class="score-value">${passPercent}%</div>
|
|
275
|
+
</div>
|
|
276
|
+
<div class="score-details">
|
|
277
|
+
<h2>Compliance Score</h2>
|
|
278
|
+
<div class="score-label">${compliance.score} of ${compliance.total} OWASP categories passing</div>
|
|
279
|
+
<div class="score-breakdown">
|
|
280
|
+
<div class="item pass">
|
|
281
|
+
<div class="num">${compliance.categories.filter(c => c.status === 'pass').length}</div>
|
|
282
|
+
<div class="lbl">Passing</div>
|
|
283
|
+
</div>
|
|
284
|
+
<div class="item warn">
|
|
285
|
+
<div class="num">${compliance.categories.filter(c => c.status === 'warn').length}</div>
|
|
286
|
+
<div class="lbl">Warning</div>
|
|
287
|
+
</div>
|
|
288
|
+
<div class="item fail">
|
|
289
|
+
<div class="num">${compliance.categories.filter(c => c.status === 'fail').length}</div>
|
|
290
|
+
<div class="lbl">Failing</div>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<h2 style="margin: 2rem 0 1rem; font-size: 1.3rem; border-bottom: 1px solid var(--border); padding-bottom: 0.5rem;">
|
|
297
|
+
Category Breakdown
|
|
298
|
+
</h2>
|
|
299
|
+
|
|
300
|
+
${categoriesHTML}
|
|
301
|
+
|
|
302
|
+
<footer>JAKU 呪 v${meta.version} — OWASP Top 10 (2021) Compliance Report</footer>
|
|
303
|
+
</body>
|
|
304
|
+
</html>`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
_escapeHtml(text) {
|
|
308
|
+
if (!text) return '';
|
|
309
|
+
return String(text)
|
|
310
|
+
.replace(/&/g, '&')
|
|
311
|
+
.replace(/</g, '<')
|
|
312
|
+
.replace(/>/g, '>')
|
|
313
|
+
.replace(/"/g, '"');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export default ComplianceReporter;
|
|
@@ -38,7 +38,7 @@ export class ReportGenerator {
|
|
|
38
38
|
const reportData = {
|
|
39
39
|
meta: {
|
|
40
40
|
agent: 'JAKU',
|
|
41
|
-
version: '1.0.
|
|
41
|
+
version: '1.0.2',
|
|
42
42
|
module: 'qa',
|
|
43
43
|
target: this.config.target_url,
|
|
44
44
|
scannedAt: new Date().toISOString(),
|
|
@@ -136,6 +136,9 @@ export class ReportGenerator {
|
|
|
136
136
|
|
|
137
137
|
for (const f of sevFindings) {
|
|
138
138
|
md += `#### ${f.id}: ${f.title}\n\n`;
|
|
139
|
+
md += `**Severity:** ${f.severity.toUpperCase()}`;
|
|
140
|
+
if (f.owasp) md += ` | **OWASP:** ${f.owasp.id} ${f.owasp.name}`;
|
|
141
|
+
md += ` \n`;
|
|
139
142
|
md += `**Affected:** ${f.affected_surface} \n`;
|
|
140
143
|
md += `**Status:** ${f.status} \n\n`;
|
|
141
144
|
md += `${f.description}\n\n`;
|
|
@@ -309,6 +312,7 @@ export class ReportGenerator {
|
|
|
309
312
|
<div class="finding-desc">${this._escapeHtml(f.description)}</div>
|
|
310
313
|
<div style="font-size:0.8rem;color:var(--text-dim);margin-top:0.5rem">
|
|
311
314
|
<strong>Affected:</strong> ${this._escapeHtml(f.affected_surface)}
|
|
315
|
+
${f.owasp ? `<span style="margin-left:1rem;padding:2px 6px;border-radius:3px;background:#1a1a25;color:#00ff88;font-size:0.7rem;font-weight:bold">${f.owasp.id} ${this._escapeHtml(f.owasp.name)}</span>` : ''}
|
|
312
316
|
</div>
|
|
313
317
|
${f.remediation ? `<div style="font-size:0.85rem;margin-top:0.5rem;color:var(--accent)"><strong>Fix:</strong> ${this._escapeHtml(f.remediation)}</div>` : ''}
|
|
314
318
|
<details class="finding-details">
|
|
@@ -141,8 +141,8 @@ export function generateSARIF(findings, meta = {}) {
|
|
|
141
141
|
tool: {
|
|
142
142
|
driver: {
|
|
143
143
|
name: 'JAKU',
|
|
144
|
-
version: meta.version || '1.0.
|
|
145
|
-
semanticVersion: meta.version || '1.0.
|
|
144
|
+
version: meta.version || '1.0.2',
|
|
145
|
+
semanticVersion: meta.version || '1.0.2',
|
|
146
146
|
informationUri: 'https://github.com/jaku-security',
|
|
147
147
|
rules,
|
|
148
148
|
},
|
package/src/utils/finding.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { nanoid } from 'nanoid';
|
|
2
|
+
import { tagFinding } from './owasp-mapper.js';
|
|
2
3
|
|
|
3
4
|
const SEVERITY_ORDER = ['critical', 'high', 'medium', 'low', 'info'];
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Creates a JAKU Finding object matching the manifest schema.
|
|
8
|
+
* Automatically tagged with OWASP Top 10 (2021) classification.
|
|
7
9
|
*/
|
|
8
10
|
export function createFinding({
|
|
9
11
|
module = 'qa',
|
|
@@ -20,7 +22,7 @@ export function createFinding({
|
|
|
20
22
|
const prefix = module.toUpperCase();
|
|
21
23
|
const shortId = nanoid(6);
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
const finding = {
|
|
24
26
|
id: `JAKU-${prefix}-${shortId}`,
|
|
25
27
|
module,
|
|
26
28
|
title,
|
|
@@ -34,6 +36,9 @@ export function createFinding({
|
|
|
34
36
|
status,
|
|
35
37
|
timestamp: new Date().toISOString(),
|
|
36
38
|
};
|
|
39
|
+
|
|
40
|
+
// Auto-tag with OWASP Top 10 classification
|
|
41
|
+
return tagFinding(finding);
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
/**
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OWASP Top 10 (2021) Mapper
|
|
3
|
+
*
|
|
4
|
+
* Maps JAKU findings to OWASP categories automatically based on
|
|
5
|
+
* finding title, module, and description patterns.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ═══════════════════════════════════════════════
|
|
9
|
+
// OWASP Top 10 (2021) Definitions
|
|
10
|
+
// ═══════════════════════════════════════════════
|
|
11
|
+
|
|
12
|
+
export const OWASP_TOP_10 = [
|
|
13
|
+
{
|
|
14
|
+
id: 'A01:2021',
|
|
15
|
+
name: 'Broken Access Control',
|
|
16
|
+
description: 'Restrictions on what authenticated users are allowed to do are often not properly enforced. Attackers can exploit these flaws to access unauthorized functionality and/or data.',
|
|
17
|
+
cwe: [22, 23, 35, 59, 200, 201, 219, 264, 275, 276, 284, 285, 352, 359, 377, 402, 425, 441, 497, 538, 540, 548, 552, 566, 601, 639, 651, 668, 706, 862, 863, 913, 922, 1275],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'A02:2021',
|
|
21
|
+
name: 'Cryptographic Failures',
|
|
22
|
+
description: 'Failures related to cryptography which often lead to sensitive data exposure. This includes use of weak cryptographic algorithms, improper key management, and transmission of data in clear text.',
|
|
23
|
+
cwe: [261, 296, 310, 319, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 335, 336, 337, 338, 340, 347, 523, 720, 757, 759, 760, 780, 818, 916],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'A03:2021',
|
|
27
|
+
name: 'Injection',
|
|
28
|
+
description: 'Injection flaws occur when untrusted data is sent to an interpreter as part of a command or query. This includes SQL, NoSQL, OS, LDAP injection, as well as XSS and prompt injection.',
|
|
29
|
+
cwe: [20, 74, 75, 77, 78, 79, 80, 83, 87, 88, 89, 90, 91, 93, 94, 95, 96, 97, 98, 99, 113, 116, 138, 184, 470, 471, 564, 610, 643, 644, 652, 917],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'A04:2021',
|
|
33
|
+
name: 'Insecure Design',
|
|
34
|
+
description: 'A broad category focusing on risks related to design and architectural flaws. Insecure design cannot be fixed by a perfect implementation — the security controls were never created to defend against specific attacks.',
|
|
35
|
+
cwe: [73, 183, 209, 213, 235, 256, 257, 266, 269, 280, 311, 312, 313, 316, 419, 430, 434, 444, 451, 472, 501, 522, 525, 539, 579, 598, 602, 642, 646, 650, 653, 656, 657, 799, 807, 840, 841, 927, 1021, 1173],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'A05:2021',
|
|
39
|
+
name: 'Security Misconfiguration',
|
|
40
|
+
description: 'Security misconfiguration is the most commonly seen issue. This includes insecure default configurations, incomplete configurations, open cloud storage, misconfigured HTTP headers, and verbose error messages.',
|
|
41
|
+
cwe: [2, 11, 13, 15, 16, 260, 315, 520, 526, 537, 541, 547, 611, 614, 756, 776, 942, 1004, 1032, 1174],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'A06:2021',
|
|
45
|
+
name: 'Vulnerable and Outdated Components',
|
|
46
|
+
description: 'Components such as libraries, frameworks, and other software modules run with the same privileges as the application. If a vulnerable component is exploited, it can facilitate serious data loss or server takeover.',
|
|
47
|
+
cwe: [1035, 1104],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'A07:2021',
|
|
51
|
+
name: 'Identification and Authentication Failures',
|
|
52
|
+
description: 'Confirmation of the user\'s identity, authentication, and session management is critical to protect against authentication-related attacks.',
|
|
53
|
+
cwe: [255, 259, 287, 288, 290, 294, 295, 297, 300, 302, 304, 306, 307, 346, 384, 521, 613, 620, 640, 798, 940, 1216],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'A08:2021',
|
|
57
|
+
name: 'Software and Data Integrity Failures',
|
|
58
|
+
description: 'Software and data integrity failures relate to code and infrastructure that does not protect against integrity violations. This includes insecure deserialization and use of software from untrusted sources.',
|
|
59
|
+
cwe: [345, 353, 426, 494, 502, 565, 784, 829, 830, 913],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'A09:2021',
|
|
63
|
+
name: 'Security Logging and Monitoring Failures',
|
|
64
|
+
description: 'Insufficient logging, detection, monitoring, and active response allows attackers to further attack systems, maintain persistence, pivot to more systems, and tamper with or extract data.',
|
|
65
|
+
cwe: [117, 223, 532, 778],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'A10:2021',
|
|
69
|
+
name: 'Server-Side Request Forgery (SSRF)',
|
|
70
|
+
description: 'SSRF flaws occur when a web application fetches a remote resource without validating the user-supplied URL. It allows an attacker to coerce the application to send a crafted request to an unexpected destination.',
|
|
71
|
+
cwe: [918],
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
// ═══════════════════════════════════════════════
|
|
76
|
+
// Pattern → OWASP Category Mapping
|
|
77
|
+
// ═══════════════════════════════════════════════
|
|
78
|
+
|
|
79
|
+
const MAPPING_RULES = [
|
|
80
|
+
// A01: Broken Access Control
|
|
81
|
+
{ pattern: /csrf|cross.site.request/i, owasp: 'A01:2021' },
|
|
82
|
+
{ pattern: /idor|insecure.direct.object/i, owasp: 'A01:2021' },
|
|
83
|
+
{ pattern: /access.control|access.boundary|authorization/i, owasp: 'A01:2021' },
|
|
84
|
+
{ pattern: /privilege.escalation|vertical.escalation|horizontal.escalation/i, owasp: 'A01:2021' },
|
|
85
|
+
{ pattern: /forced.browsing|directory.listing/i, owasp: 'A01:2021' },
|
|
86
|
+
{ pattern: /cors.misconfig|cors.origin|cors.credential/i, owasp: 'A01:2021' },
|
|
87
|
+
{ pattern: /clickjacking|x-frame/i, owasp: 'A01:2021' },
|
|
88
|
+
{ pattern: /open.redirect/i, owasp: 'A01:2021' },
|
|
89
|
+
{ pattern: /guest.*access|missing.*auth.*endpoint/i, owasp: 'A01:2021' },
|
|
90
|
+
{ pattern: /account.takeover/i, owasp: 'A01:2021' },
|
|
91
|
+
{ pattern: /feature.flag.bypass/i, owasp: 'A01:2021' },
|
|
92
|
+
|
|
93
|
+
// A02: Cryptographic Failures
|
|
94
|
+
{ pattern: /secret|api.key|token.*exposed|credential.*leak|password.*exposed/i, owasp: 'A02:2021' },
|
|
95
|
+
{ pattern: /tls|ssl|certificate|weak.cipher/i, owasp: 'A02:2021' },
|
|
96
|
+
{ pattern: /hsts|http.*not.*redirect|cleartext/i, owasp: 'A02:2021' },
|
|
97
|
+
{ pattern: /cookie.*secure|cookie.*httponly|cookie.*samesite/i, owasp: 'A02:2021' },
|
|
98
|
+
{ pattern: /sensitive.data.*exposure|data.*leak/i, owasp: 'A02:2021' },
|
|
99
|
+
|
|
100
|
+
// A03: Injection
|
|
101
|
+
{ pattern: /xss|cross.site.script/i, owasp: 'A03:2021' },
|
|
102
|
+
{ pattern: /sql.injection|sqli/i, owasp: 'A03:2021' },
|
|
103
|
+
{ pattern: /prompt.injection|prompt.*inject/i, owasp: 'A03:2021' },
|
|
104
|
+
{ pattern: /command.injection|os.command/i, owasp: 'A03:2021' },
|
|
105
|
+
{ pattern: /ldap.injection/i, owasp: 'A03:2021' },
|
|
106
|
+
{ pattern: /nosql.injection/i, owasp: 'A03:2021' },
|
|
107
|
+
{ pattern: /header.injection|crlf/i, owasp: 'A03:2021' },
|
|
108
|
+
{ pattern: /template.injection|ssti/i, owasp: 'A03:2021' },
|
|
109
|
+
{ pattern: /jailbreak/i, owasp: 'A03:2021' },
|
|
110
|
+
{ pattern: /guardrail.bypass/i, owasp: 'A03:2021' },
|
|
111
|
+
{ pattern: /ai.mediated.xss|unsanitized.*output/i, owasp: 'A03:2021' },
|
|
112
|
+
|
|
113
|
+
// A04: Insecure Design
|
|
114
|
+
{ pattern: /race.condition/i, owasp: 'A04:2021' },
|
|
115
|
+
{ pattern: /business.logic|workflow.violation/i, owasp: 'A04:2021' },
|
|
116
|
+
{ pattern: /pricing.manipulation|price.*tamper/i, owasp: 'A04:2021' },
|
|
117
|
+
{ pattern: /coupon.*abuse|discount.*abuse|promo.*abuse/i, owasp: 'A04:2021' },
|
|
118
|
+
{ pattern: /cart.*manipulation|quantity.*manipulation/i, owasp: 'A04:2021' },
|
|
119
|
+
{ pattern: /email.enumeration/i, owasp: 'A04:2021' },
|
|
120
|
+
{ pattern: /rate.limit|brute.force/i, owasp: 'A04:2021' },
|
|
121
|
+
{ pattern: /missing.*captcha/i, owasp: 'A04:2021' },
|
|
122
|
+
{ pattern: /file.upload.*bypass|unrestricted.*upload/i, owasp: 'A04:2021' },
|
|
123
|
+
{ pattern: /excessive.agency/i, owasp: 'A04:2021' },
|
|
124
|
+
|
|
125
|
+
// A05: Security Misconfiguration
|
|
126
|
+
{ pattern: /missing.*header|security.header/i, owasp: 'A05:2021' },
|
|
127
|
+
{ pattern: /content.security.policy|csp/i, owasp: 'A05:2021' },
|
|
128
|
+
{ pattern: /verbose.error|error.*disclosure|stack.trace/i, owasp: 'A05:2021' },
|
|
129
|
+
{ pattern: /directory.listing|exposed.*directory/i, owasp: 'A05:2021' },
|
|
130
|
+
{ pattern: /debug.*endpoint|admin.*exposed|management.*endpoint/i, owasp: 'A05:2021' },
|
|
131
|
+
{ pattern: /default.credential|default.password/i, owasp: 'A05:2021' },
|
|
132
|
+
{ pattern: /information.disclosure/i, owasp: 'A05:2021' },
|
|
133
|
+
{ pattern: /graphql.*introspection/i, owasp: 'A05:2021' },
|
|
134
|
+
{ pattern: /subdomain/i, owasp: 'A05:2021' },
|
|
135
|
+
{ pattern: /misconfigur/i, owasp: 'A05:2021' },
|
|
136
|
+
|
|
137
|
+
// A06: Vulnerable and Outdated Components
|
|
138
|
+
{ pattern: /dependency.*vuln|outdated.*package|known.*vuln|cve-/i, owasp: 'A06:2021' },
|
|
139
|
+
{ pattern: /vulnerable.*component|vulnerable.*library/i, owasp: 'A06:2021' },
|
|
140
|
+
{ pattern: /npm.*audit|dependency.*audit/i, owasp: 'A06:2021' },
|
|
141
|
+
|
|
142
|
+
// A07: Identification and Authentication Failures
|
|
143
|
+
{ pattern: /auth.*bypass|broken.*auth|authentication.*failure/i, owasp: 'A07:2021' },
|
|
144
|
+
{ pattern: /jwt.*none|jwt.*signature|jwt.*weak/i, owasp: 'A07:2021' },
|
|
145
|
+
{ pattern: /session.*fixation|session.*hijack/i, owasp: 'A07:2021' },
|
|
146
|
+
{ pattern: /weak.*password|password.*policy/i, owasp: 'A07:2021' },
|
|
147
|
+
{ pattern: /oauth.*misconfig|oauth.*vuln/i, owasp: 'A07:2021' },
|
|
148
|
+
{ pattern: /api.key.*no.*rotation|api.key.*weak/i, owasp: 'A07:2021' },
|
|
149
|
+
|
|
150
|
+
// A08: Software and Data Integrity Failures
|
|
151
|
+
{ pattern: /deserialization|prototype.pollution/i, owasp: 'A08:2021' },
|
|
152
|
+
{ pattern: /subresource.integrity|sri/i, owasp: 'A08:2021' },
|
|
153
|
+
{ pattern: /untrusted.*source|supply.chain/i, owasp: 'A08:2021' },
|
|
154
|
+
|
|
155
|
+
// A09: Security Logging and Monitoring Failures
|
|
156
|
+
{ pattern: /logging.*failure|no.*logging|insufficient.*log/i, owasp: 'A09:2021' },
|
|
157
|
+
{ pattern: /monitoring.*gap|no.*monitoring/i, owasp: 'A09:2021' },
|
|
158
|
+
|
|
159
|
+
// A10: Server-Side Request Forgery
|
|
160
|
+
{ pattern: /ssrf|server.side.request/i, owasp: 'A10:2021' },
|
|
161
|
+
{ pattern: /cloud.*metadata|169\.254/i, owasp: 'A10:2021' },
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
// ═══════════════════════════════════════════════
|
|
165
|
+
// Module-level fallback mapping
|
|
166
|
+
// ═══════════════════════════════════════════════
|
|
167
|
+
|
|
168
|
+
const MODULE_FALLBACK = {
|
|
169
|
+
security: 'A05:2021', // Security misconfiguration as default for security findings
|
|
170
|
+
ai: 'A03:2021', // Injection for AI findings
|
|
171
|
+
logic: 'A04:2021', // Insecure design for business logic findings
|
|
172
|
+
api: 'A07:2021', // Auth failures for API findings
|
|
173
|
+
qa: null, // QA findings may not map to OWASP
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// ═══════════════════════════════════════════════
|
|
177
|
+
// Public API
|
|
178
|
+
// ═══════════════════════════════════════════════
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Look up the OWASP category for a given finding.
|
|
182
|
+
* Returns { id, name } or null if no mapping found.
|
|
183
|
+
*/
|
|
184
|
+
export function classifyFinding(finding) {
|
|
185
|
+
const text = `${finding.title} ${finding.description || ''}`;
|
|
186
|
+
|
|
187
|
+
// Try pattern matching first (most specific)
|
|
188
|
+
for (const rule of MAPPING_RULES) {
|
|
189
|
+
if (rule.pattern.test(text)) {
|
|
190
|
+
const cat = OWASP_TOP_10.find(c => c.id === rule.owasp);
|
|
191
|
+
return { id: cat.id, name: cat.name };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Fall back to module-level mapping
|
|
196
|
+
const fallbackId = MODULE_FALLBACK[finding.module];
|
|
197
|
+
if (fallbackId) {
|
|
198
|
+
const cat = OWASP_TOP_10.find(c => c.id === fallbackId);
|
|
199
|
+
return { id: cat.id, name: cat.name };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Enrich a finding with its OWASP classification.
|
|
207
|
+
* Adds `owasp` field: { id: 'A03:2021', name: 'Injection' }
|
|
208
|
+
*/
|
|
209
|
+
export function tagFinding(finding) {
|
|
210
|
+
finding.owasp = classifyFinding(finding);
|
|
211
|
+
return finding;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Generate a compliance status for all OWASP Top 10 categories.
|
|
216
|
+
*
|
|
217
|
+
* @param {Array} findings - Array of findings (should already have `owasp` field)
|
|
218
|
+
* @returns {Object} - { score, total, categories: [ { id, name, status, findings, ... } ] }
|
|
219
|
+
*/
|
|
220
|
+
export function getComplianceStatus(findings) {
|
|
221
|
+
const categories = OWASP_TOP_10.map(cat => {
|
|
222
|
+
const matched = findings.filter(f => f.owasp?.id === cat.id);
|
|
223
|
+
const criticalOrHigh = matched.filter(f => f.severity === 'critical' || f.severity === 'high');
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
id: cat.id,
|
|
227
|
+
name: cat.name,
|
|
228
|
+
description: cat.description,
|
|
229
|
+
status: matched.length === 0 ? 'pass' : criticalOrHigh.length > 0 ? 'fail' : 'warn',
|
|
230
|
+
findingsCount: matched.length,
|
|
231
|
+
criticalCount: matched.filter(f => f.severity === 'critical').length,
|
|
232
|
+
highCount: matched.filter(f => f.severity === 'high').length,
|
|
233
|
+
mediumCount: matched.filter(f => f.severity === 'medium').length,
|
|
234
|
+
lowCount: matched.filter(f => f.severity === 'low').length,
|
|
235
|
+
infoCount: matched.filter(f => f.severity === 'info').length,
|
|
236
|
+
findings: matched,
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const passing = categories.filter(c => c.status === 'pass').length;
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
framework: 'OWASP Top 10 (2021)',
|
|
244
|
+
score: passing,
|
|
245
|
+
total: OWASP_TOP_10.length,
|
|
246
|
+
percentage: Math.round((passing / OWASP_TOP_10.length) * 100),
|
|
247
|
+
categories,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export default { OWASP_TOP_10, classifyFinding, tagFinding, getComplianceStatus };
|