kafkacode 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +2 -2
- package/dist/ReportGenerator.js +94 -0
- package/dist/cli.js +36 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.4.0] - 2026-06-05
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Machine-readable output: `--format json` and `--format sarif`.
|
|
9
|
+
- SARIF 2.1.0 integrates with GitHub code scanning (Security tab + inline PR annotations).
|
|
10
|
+
- `--output <file>` writes the report to a file instead of stdout.
|
|
11
|
+
- `--no-fail` exits `0` even when issues are found (useful when uploading SARIF).
|
|
12
|
+
|
|
5
13
|
## [1.3.0] - 2026-06-05
|
|
6
14
|
|
|
7
15
|
### Added
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ a clear **A+ → F privacy grade**, and CI-ready exit codes. Runs in seconds.
|
|
|
16
16
|
[](package.json)
|
|
17
17
|
[](CONTRIBUTING.md)
|
|
18
18
|
|
|
19
|
-
[
|
|
19
|
+
[📖 Documentation](https://nikhil-kapu.github.io/kafkacode/) · [Quickstart](#-quickstart) · [Features](#-features) · [CI/CD](#-cicd-integration) · [Contributing](#-contributing)
|
|
20
20
|
|
|
21
21
|
</div>
|
|
22
22
|
|
|
@@ -195,7 +195,7 @@ deep secret scanners rather than replacing them.
|
|
|
195
195
|
## 🗺️ Roadmap
|
|
196
196
|
|
|
197
197
|
- [x] **Bring-your-own-key AI** — call Groq / OpenAI-compatible providers directly
|
|
198
|
-
- [
|
|
198
|
+
- [x] **`--json` & SARIF output** — SARIF integrates with the GitHub Security tab
|
|
199
199
|
- [ ] Config file & `.kafkacodeignore`
|
|
200
200
|
- [ ] Baseline file to adopt on existing codebases
|
|
201
201
|
- [ ] More file types (`.env`, YAML, Terraform, Dockerfiles)
|
package/dist/ReportGenerator.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
let VERSION = '0.0.0';
|
|
5
|
+
try {
|
|
6
|
+
VERSION = require('../package.json').version;
|
|
7
|
+
} catch (_) { /* fall back to the default */ }
|
|
2
8
|
|
|
3
9
|
class ReportGenerator {
|
|
4
10
|
constructor() {
|
|
@@ -66,6 +72,94 @@ class ReportGenerator {
|
|
|
66
72
|
return { grade, url, markdown: `` };
|
|
67
73
|
}
|
|
68
74
|
|
|
75
|
+
// Public: render findings as a structured JSON report.
|
|
76
|
+
generateJson(scanDir, findings, fileCount) {
|
|
77
|
+
const severityCounts = { Critical: 0, High: 0, Medium: 0, Low: 0 };
|
|
78
|
+
for (const f of findings) {
|
|
79
|
+
const s = f.severity || 'Low';
|
|
80
|
+
if (Object.prototype.hasOwnProperty.call(severityCounts, s)) severityCounts[s]++;
|
|
81
|
+
}
|
|
82
|
+
const cwd = process.cwd();
|
|
83
|
+
const rel = (p) => (p ? path.relative(cwd, p).split(path.sep).join('/') : '');
|
|
84
|
+
const report = {
|
|
85
|
+
tool: 'kafkacode',
|
|
86
|
+
version: VERSION,
|
|
87
|
+
directory: scanDir,
|
|
88
|
+
timestamp: this.reportTime.toISOString(),
|
|
89
|
+
summary: {
|
|
90
|
+
filesScanned: fileCount,
|
|
91
|
+
totalIssues: findings.length,
|
|
92
|
+
grade: this._calculateGrade(findings),
|
|
93
|
+
severityCounts
|
|
94
|
+
},
|
|
95
|
+
findings: findings.map((f) => ({
|
|
96
|
+
file: rel(f.file_path),
|
|
97
|
+
line: f.line_number || 0,
|
|
98
|
+
severity: f.severity || 'Low',
|
|
99
|
+
type: f.finding_type || 'Issue',
|
|
100
|
+
description: f.description || '',
|
|
101
|
+
suggestion: f.suggestion || '',
|
|
102
|
+
source: f.source === 'llm' ? 'ai' : 'pattern',
|
|
103
|
+
snippet: f.code_snippet || ''
|
|
104
|
+
}))
|
|
105
|
+
};
|
|
106
|
+
return JSON.stringify(report, null, 2);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Public: render findings as SARIF 2.1.0 (for GitHub code scanning).
|
|
110
|
+
generateSarif(findings) {
|
|
111
|
+
const cwd = process.cwd();
|
|
112
|
+
const rel = (p) => (p ? path.relative(cwd, p).split(path.sep).join('/') : 'unknown');
|
|
113
|
+
const levelFor = (sev) => {
|
|
114
|
+
if (sev === 'Critical' || sev === 'High') return 'error';
|
|
115
|
+
if (sev === 'Medium') return 'warning';
|
|
116
|
+
return 'note';
|
|
117
|
+
};
|
|
118
|
+
const slug = (s) => ((s || 'issue').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '') || 'issue');
|
|
119
|
+
|
|
120
|
+
const rules = new Map();
|
|
121
|
+
for (const f of findings) {
|
|
122
|
+
const id = slug(f.finding_type);
|
|
123
|
+
if (!rules.has(id)) {
|
|
124
|
+
rules.set(id, {
|
|
125
|
+
id,
|
|
126
|
+
name: f.finding_type || 'Issue',
|
|
127
|
+
shortDescription: { text: f.finding_type || 'Issue' },
|
|
128
|
+
defaultConfiguration: { level: levelFor(f.severity) }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const results = findings.map((f) => ({
|
|
134
|
+
ruleId: slug(f.finding_type),
|
|
135
|
+
level: levelFor(f.severity),
|
|
136
|
+
message: { text: f.description || 'Privacy issue detected' },
|
|
137
|
+
locations: [{
|
|
138
|
+
physicalLocation: {
|
|
139
|
+
artifactLocation: { uri: rel(f.file_path) },
|
|
140
|
+
region: { startLine: Math.max(1, f.line_number || 1) }
|
|
141
|
+
}
|
|
142
|
+
}]
|
|
143
|
+
}));
|
|
144
|
+
|
|
145
|
+
const sarif = {
|
|
146
|
+
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
|
|
147
|
+
version: '2.1.0',
|
|
148
|
+
runs: [{
|
|
149
|
+
tool: {
|
|
150
|
+
driver: {
|
|
151
|
+
name: 'KafkaCode',
|
|
152
|
+
informationUri: 'https://github.com/nikhil-kapu/kafkacode',
|
|
153
|
+
version: VERSION,
|
|
154
|
+
rules: Array.from(rules.values())
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
results
|
|
158
|
+
}]
|
|
159
|
+
};
|
|
160
|
+
return JSON.stringify(sarif, null, 2);
|
|
161
|
+
}
|
|
162
|
+
|
|
69
163
|
_groupFindingsBySeverity(findings) {
|
|
70
164
|
const groups = {};
|
|
71
165
|
this.severityOrder.forEach(severity => {
|
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ const program = new Command();
|
|
|
12
12
|
program
|
|
13
13
|
.name('kafkacode')
|
|
14
14
|
.description('KafkaCode - Privacy and Compliance Scanner')
|
|
15
|
-
.version('1.
|
|
15
|
+
.version('1.4.0');
|
|
16
16
|
|
|
17
17
|
program
|
|
18
18
|
.command('scan')
|
|
@@ -20,7 +20,10 @@ program
|
|
|
20
20
|
.argument('<directory>', 'Path to the source code directory to scan')
|
|
21
21
|
.option('-v, --verbose', 'Print verbose progress updates during the scan')
|
|
22
22
|
.option('-b, --badge', 'Print a copy-paste privacy-grade badge for your README')
|
|
23
|
+
.option('-f, --format <format>', 'Output format: console, json, or sarif', 'console')
|
|
24
|
+
.option('-o, --output <file>', 'Write output to a file instead of stdout')
|
|
23
25
|
.option('--no-ai', 'Disable AI-powered analysis (run pattern scan only)')
|
|
26
|
+
.option('--no-fail', 'Exit 0 even when issues are found')
|
|
24
27
|
.action(async (directory, options) => {
|
|
25
28
|
await runScan(directory, options);
|
|
26
29
|
});
|
|
@@ -68,24 +71,43 @@ async function runScan(directory, options = {}) {
|
|
|
68
71
|
}
|
|
69
72
|
const findings = await analysisEngine.analyzeFiles(files);
|
|
70
73
|
|
|
71
|
-
//
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
+
// Render the findings in the requested format
|
|
75
|
+
const format = (options.format || 'console').toLowerCase();
|
|
76
|
+
let output;
|
|
77
|
+
if (format === 'json') {
|
|
78
|
+
output = reportGenerator.generateJson(directory, findings, files.length);
|
|
79
|
+
} else if (format === 'sarif') {
|
|
80
|
+
output = reportGenerator.generateSarif(findings);
|
|
81
|
+
} else if (format === 'console') {
|
|
82
|
+
output = reportGenerator.generateReport(directory, findings, files.length);
|
|
83
|
+
} else {
|
|
84
|
+
console.error(`Error: unknown --format '${options.format}'. Use 'console', 'json', or 'sarif'.`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
74
87
|
|
|
75
|
-
//
|
|
76
|
-
if (options.
|
|
77
|
-
|
|
88
|
+
// Write to a file, or print to stdout
|
|
89
|
+
if (options.output) {
|
|
90
|
+
fs.writeFileSync(options.output, output);
|
|
91
|
+
console.error(`✅ Wrote ${format} output to ${options.output}`);
|
|
92
|
+
} else {
|
|
93
|
+
console.log(output);
|
|
78
94
|
}
|
|
79
95
|
|
|
80
|
-
//
|
|
81
|
-
if (options.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
// Console-only extras — kept out of machine-readable output
|
|
97
|
+
if (format === 'console' && !options.output) {
|
|
98
|
+
if (options.ai !== false && !analysisEngine.aiEnabled()) {
|
|
99
|
+
console.log('💡 Tip: set KAFKACODE_API_KEY to enable AI-powered contextual analysis. See the README.\n');
|
|
100
|
+
}
|
|
101
|
+
if (options.badge) {
|
|
102
|
+
const badge = reportGenerator.getBadge(findings);
|
|
103
|
+
console.log('🏷️ Privacy Grade Badge — paste into your README:\n');
|
|
104
|
+
console.log(` ${badge.markdown}\n`);
|
|
105
|
+
}
|
|
85
106
|
}
|
|
86
107
|
|
|
87
|
-
//
|
|
88
|
-
|
|
108
|
+
// Exit non-zero when issues are found, unless --no-fail was passed
|
|
109
|
+
const shouldFail = options.fail !== false && findings.length > 0;
|
|
110
|
+
process.exit(shouldFail ? 1 : 0);
|
|
89
111
|
|
|
90
112
|
} catch (error) {
|
|
91
113
|
if (error.name === 'AbortError' || error.message.includes('aborted')) {
|
package/package.json
CHANGED