jaku.sh 1.0.1 → 1.0.3

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 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
- # Option A: Clone & install (development)
32
- git clone https://github.com/theshantanupandey/jaku.git
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 --verbose
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
- # AI abuse testing only
47
- jaku ai https://your-ai-app.dev --verbose
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.1 · Multi-Agent
470
+ ╚╝╩ ╩╩ ╩╚═╝ v1.0.3 · 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.1*';
220
+ body += '\n---\n*Scanned by [JAKU](https://github.com/jaku-security/jaku) v1.0.3*';
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.1",
3
+ "version": "1.0.3",
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",
@@ -18,6 +18,7 @@
18
18
  ],
19
19
  "scripts": {
20
20
  "scan": "node src/cli.js scan",
21
+ "postinstall": "npx playwright install chromium 2>/dev/null || echo '⚠ JAKU: Could not auto-install Chromium. Run: npx playwright install chromium'",
21
22
  "prepublishOnly": "node src/cli.js --help"
22
23
  },
23
24
  "keywords": [
@@ -42,7 +43,7 @@
42
43
  "url": "https://github.com/theshantanupandey"
43
44
  },
44
45
  "license": "SEE LICENSE IN LICENSE",
45
- "homepage": "https://jakusec.dev",
46
+ "homepage": "https://jaku.app",
46
47
  "repository": {
47
48
  "type": "git",
48
49
  "url": "https://github.com/theshantanupandey/jaku.git"
@@ -255,7 +255,7 @@ export class Orchestrator {
255
255
  try {
256
256
  const payload = {
257
257
  agent: 'JAKU',
258
- version: '1.0.1',
258
+ version: '1.0.3',
259
259
  target: this.config.target_url,
260
260
  timestamp: new Date().toISOString(),
261
261
  duration: results.duration,
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.1 · Multi-Agent')}
21
+ ${chalk.hex('#00ff88').bold(' ╚╝╩ ╩╩ ╩╚═╝')} ${chalk.dim('v1.0.3 · 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.1');
29
+ .version('1.0.3');
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.3', 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')
@@ -45,7 +45,28 @@ export class Crawler {
45
45
  */
46
46
  async crawl(targetUrl, authState = null, seedLinks = []) {
47
47
  this.baseUrl = new URL(targetUrl);
48
- const browser = await chromium.launch({ headless: true });
48
+
49
+ let browser;
50
+ try {
51
+ browser = await chromium.launch({ headless: true });
52
+ } catch (err) {
53
+ if (err.message.includes("Executable doesn't exist") || err.message.includes('playwright install')) {
54
+ this.logger?.warn?.('Chromium not found — attempting automatic install...');
55
+ const { execSync } = await import('child_process');
56
+ try {
57
+ execSync('npx playwright install chromium', { stdio: 'inherit', timeout: 120000 });
58
+ browser = await chromium.launch({ headless: true });
59
+ } catch {
60
+ throw new Error(
61
+ 'Playwright Chromium is not installed. Run:\n\n' +
62
+ ' npx playwright install chromium\n\n' +
63
+ 'Then re-run your jaku command.'
64
+ );
65
+ }
66
+ } else {
67
+ throw err;
68
+ }
69
+ }
49
70
 
50
71
  const contextOptions = {
51
72
  viewport: { width: 1440, height: 900 },
@@ -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, '&amp;')
311
+ .replace(/</g, '&lt;')
312
+ .replace(/>/g, '&gt;')
313
+ .replace(/"/g, '&quot;');
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.1',
41
+ version: '1.0.3',
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.1',
145
- semanticVersion: meta.version || '1.0.1',
144
+ version: meta.version || '1.0.3',
145
+ semanticVersion: meta.version || '1.0.3',
146
146
  informationUri: 'https://github.com/jaku-security',
147
147
  rules,
148
148
  },
@@ -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
- return {
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 };