kafkacode 1.2.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 +26 -0
- package/LICENSE +21 -0
- package/README.md +98 -0
- package/dist/AnalysisEngine.js +53 -0
- package/dist/FileScanner.js +96 -0
- package/dist/LLMAnalyzer.js +286 -0
- package/dist/PatternScanner.js +135 -0
- package/dist/ReportGenerator.js +229 -0
- package/dist/cli.js +93 -0
- package/dist/index.js +13 -0
- package/package.json +57 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [1.0.0] - 2024-09-22
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Initial release of KafkaCode Privacy Scanner
|
|
9
|
+
- Pattern-based detection for secrets, API keys, and sensitive data
|
|
10
|
+
- AI-powered contextual analysis using Grok LLM
|
|
11
|
+
- Support for multiple programming languages (Python, JavaScript, TypeScript, Java, Go, Ruby, PHP)
|
|
12
|
+
- Beautiful console reporting with severity levels
|
|
13
|
+
- Privacy grading system (A+ to F)
|
|
14
|
+
- CLI interface with scan command
|
|
15
|
+
- API key obfuscation for commercial distribution
|
|
16
|
+
- Gitignore pattern support
|
|
17
|
+
- Comprehensive test suite
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
- Detects AWS keys, Stripe keys, private keys
|
|
21
|
+
- Identifies PII like emails, phone numbers, IP addresses
|
|
22
|
+
- High entropy string detection for potential secrets
|
|
23
|
+
- Context-aware privacy vulnerability analysis
|
|
24
|
+
- Detailed suggestions for remediation
|
|
25
|
+
- Verbose logging option
|
|
26
|
+
- Exit codes for CI/CD integration
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 KafkaLabs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# KafkaCode Privacy Scanner
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
<h3>by <a href="https://kafkalabs.com">KafkaLabs</a></h3>
|
|
5
|
+
<p>š <strong>Shift-left privacy and compliance scanner for source code</strong></p>
|
|
6
|
+
<p>
|
|
7
|
+
<a href="https://kafkalabs.com/kafka-code">Website</a> ā¢
|
|
8
|
+
<a href="https://github.com/nikhil-kapu/KafkacodeFnpm">GitHub</a> ā¢
|
|
9
|
+
<a href="https://www.npmjs.com/package/kafkacode">npm</a>
|
|
10
|
+
</p>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
KafkaCode is an AI-powered privacy scanner by **KafkaLabs** that helps developers identify potential privacy issues, PII leaks, and compliance violations in their source code before they reach production.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- š **Pattern-based Detection**: Identifies hardcoded secrets, API keys, and sensitive data
|
|
20
|
+
- š¤ **AI-powered Analysis**: Uses advanced LLM analysis for contextual privacy issues
|
|
21
|
+
- ā” **Fast & Efficient**: Scans entire codebases in seconds
|
|
22
|
+
- šÆ **Multiple File Types**: Supports Python, JavaScript, TypeScript, Java, Go, Ruby, PHP
|
|
23
|
+
- š **Detailed Reports**: Beautiful console reports with severity levels
|
|
24
|
+
- š **CI/CD Ready**: Easy integration with build pipelines
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install -g kafkacode
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or using npx (no installation required):
|
|
33
|
+
```bash
|
|
34
|
+
npx kafkacode scan /path/to/your/project
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
**Basic Scan:**
|
|
40
|
+
```bash
|
|
41
|
+
kafkacode scan /path/to/your/project
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Verbose Output:**
|
|
45
|
+
```bash
|
|
46
|
+
kafkacode scan /path/to/your/project --verbose
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## What it detects
|
|
50
|
+
|
|
51
|
+
- **Critical Issues**: AWS keys, Stripe keys, Private keys
|
|
52
|
+
- **High Severity**: Sensitive keywords in assignment context
|
|
53
|
+
- **Medium Severity**: Email addresses, Phone numbers, High entropy strings
|
|
54
|
+
- **Low Severity**: IP addresses, URLs
|
|
55
|
+
|
|
56
|
+
## Privacy Grade
|
|
57
|
+
|
|
58
|
+
KafkaCode assigns a privacy grade (A+ to F) based on the severity and number of issues found:
|
|
59
|
+
|
|
60
|
+
- **A+/A/A-**: Excellent privacy practices
|
|
61
|
+
- **B+/B/B-**: Good privacy practices with minor issues
|
|
62
|
+
- **C+/C/C-**: Moderate privacy issues that should be addressed
|
|
63
|
+
- **D**: Multiple high-severity privacy issues
|
|
64
|
+
- **F**: Critical privacy vulnerabilities detected
|
|
65
|
+
|
|
66
|
+
## Example Output
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
šÆ PRIVACY SCAN REPORT
|
|
70
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
71
|
+
|
|
72
|
+
š SCAN SUMMARY
|
|
73
|
+
š Directory: ./src
|
|
74
|
+
ā° Timestamp: 2024-01-15 10:30:45
|
|
75
|
+
š Files Scanned: 25
|
|
76
|
+
š Total Issues: 3
|
|
77
|
+
š Privacy Grade: š”B-
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT License - Copyright (c) 2025 KafkaLabs
|
|
83
|
+
|
|
84
|
+
See [LICENSE](LICENSE) file for details.
|
|
85
|
+
|
|
86
|
+
## About KafkaLabs
|
|
87
|
+
|
|
88
|
+
KafkaCode is built by [KafkaLabs](https://kafkalabs.com), helping developers build privacy-first applications.
|
|
89
|
+
|
|
90
|
+
- š **Website**: [kafkalabs.com/kafka-code](https://kafkalabs.com/kafka-code)
|
|
91
|
+
- š§ **Contact**: contact@kafkalabs.com
|
|
92
|
+
- š¬ **Issues**: [GitHub Issues](https://github.com/nikhil-kapu/KafkacodeFnpm/issues)
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
<div align="center">
|
|
97
|
+
Made with ā¤ļø by <a href="https://kafkalabs.com">KafkaLabs</a>
|
|
98
|
+
</div>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const PatternScanner = require('./PatternScanner');
|
|
3
|
+
const LLMAnalyzer = require('./LLMAnalyzer');
|
|
4
|
+
|
|
5
|
+
class AnalysisEngine {
|
|
6
|
+
constructor(verbose = false) {
|
|
7
|
+
this.verbose = verbose;
|
|
8
|
+
this.patternScanner = new PatternScanner();
|
|
9
|
+
this.llmAnalyzer = new LLMAnalyzer();
|
|
10
|
+
this.llmAnalyzer.verbose = verbose;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async analyzeFile(filePath) {
|
|
14
|
+
if (this.verbose) {
|
|
15
|
+
console.log(`Analyzing: ${filePath}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let content;
|
|
19
|
+
try {
|
|
20
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (this.verbose) {
|
|
23
|
+
console.log(`Error reading ${filePath}: ${error.message}`);
|
|
24
|
+
}
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const findings = [];
|
|
29
|
+
|
|
30
|
+
// Pattern-based analysis first
|
|
31
|
+
const patternFindings = this.patternScanner.scanContent(filePath, content);
|
|
32
|
+
findings.push(...patternFindings);
|
|
33
|
+
|
|
34
|
+
// LLM-based analysis with pattern findings as context
|
|
35
|
+
const llmFindings = await this.llmAnalyzer.analyzeFile(filePath, content, patternFindings);
|
|
36
|
+
findings.push(...llmFindings);
|
|
37
|
+
|
|
38
|
+
return findings;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async analyzeFiles(filePaths) {
|
|
42
|
+
const allFindings = [];
|
|
43
|
+
|
|
44
|
+
for (const filePath of filePaths) {
|
|
45
|
+
const fileFindings = await this.analyzeFile(filePath);
|
|
46
|
+
allFindings.push(...fileFindings);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return allFindings;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = AnalysisEngine;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { minimatch } = require('minimatch');
|
|
4
|
+
|
|
5
|
+
class FileScanner {
|
|
6
|
+
constructor(rootDir) {
|
|
7
|
+
this.rootDir = path.resolve(rootDir);
|
|
8
|
+
this.supportedExtensions = new Set(['.py', '.js', '.ts', '.java', '.go', '.rb', '.php']);
|
|
9
|
+
this.ignoreDirs = new Set([
|
|
10
|
+
'.git', 'node_modules', 'venv', '__pycache__', '.venv', 'env',
|
|
11
|
+
'build', 'dist', 'target', 'out', '.next', '.nuxt', 'vendor',
|
|
12
|
+
'coverage', '.coverage', '.pytest_cache', '.mypy_cache'
|
|
13
|
+
]);
|
|
14
|
+
this.gitignorePatterns = this._loadGitignore();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_loadGitignore() {
|
|
18
|
+
const gitignorePath = path.join(this.rootDir, '.gitignore');
|
|
19
|
+
const patterns = [];
|
|
20
|
+
|
|
21
|
+
if (fs.existsSync(gitignorePath)) {
|
|
22
|
+
try {
|
|
23
|
+
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
24
|
+
const lines = content.split('\n');
|
|
25
|
+
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
const trimmed = line.trim();
|
|
28
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
29
|
+
patterns.push(trimmed);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// Ignore errors loading gitignore
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return patterns;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_shouldIgnorePath(filePath) {
|
|
41
|
+
const relativePath = path.relative(this.rootDir, filePath);
|
|
42
|
+
const pathParts = relativePath.split(path.sep);
|
|
43
|
+
|
|
44
|
+
// Check built-in ignore directories
|
|
45
|
+
for (const part of pathParts) {
|
|
46
|
+
if (this.ignoreDirs.has(part)) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check gitignore patterns
|
|
52
|
+
for (const pattern of this.gitignorePatterns) {
|
|
53
|
+
if (minimatch(relativePath, pattern) || minimatch(path.basename(filePath), pattern)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_scanDirectory(dir) {
|
|
62
|
+
const files = [];
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
66
|
+
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const fullPath = path.join(dir, entry.name);
|
|
69
|
+
|
|
70
|
+
if (this._shouldIgnorePath(fullPath)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (entry.isDirectory()) {
|
|
75
|
+
files.push(...this._scanDirectory(fullPath));
|
|
76
|
+
} else if (entry.isFile()) {
|
|
77
|
+
const ext = path.extname(entry.name);
|
|
78
|
+
if (this.supportedExtensions.has(ext)) {
|
|
79
|
+
files.push(fullPath);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// Ignore directory access errors
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return files;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
scanFiles() {
|
|
91
|
+
const files = this._scanDirectory(this.rootDir);
|
|
92
|
+
return files.sort();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = FileScanner;
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
|
|
3
|
+
class LLMAnalyzer {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.backendEndpoint = process.env.KAFKACODE_BACKEND_ENDPOINT || 'https://adorable-motivation-production.up.railway.app';
|
|
6
|
+
this.verbose = false;
|
|
7
|
+
this.interestKeywords = new Set([
|
|
8
|
+
'api', 'db', 'database', 'user', 'password', 'save', 'fetch', 'send', 'log',
|
|
9
|
+
'auth', 'token', 'key', 'secret', 'credential', 'email', 'phone', 'address',
|
|
10
|
+
'personal', 'sensitive', 'private', 'encrypt', 'decrypt', 'hash'
|
|
11
|
+
]);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_createSnippetPrompt(codeSnippet, filePath, startLine) {
|
|
15
|
+
return `SYSTEM: You are an automated privacy and compliance analysis engine. Your task is to review the following CODE SNIPPET and identify potential privacy vulnerabilities based ONLY on the provided code. The snippet is from a larger file. Do not infer functionality outside of this snippet. Your analysis must focus on how the code handles data that could be considered sensitive or PII.
|
|
16
|
+
|
|
17
|
+
Your response MUST be a single, valid JSON object. The root object should contain a single key: "vulnerabilities". The value of "vulnerabilities" must be an array of objects. Each object in the array represents a single, distinct vulnerability and must have the following keys:
|
|
18
|
+
- "line_number": The integer line number where the vulnerability is found. This number MUST be relative to the original file, not the snippet.
|
|
19
|
+
- "severity": A string, which must be one of "High", "Medium", or "Low".
|
|
20
|
+
- "description": A concise, one-sentence string describing the vulnerability (e.g., "User email is logged to a publicly accessible file.").
|
|
21
|
+
- "suggestion": A one-sentence string providing an actionable recommendation for the developer (e.g., "Consider logging only non-sensitive user identifiers or hashing the data before logging.").
|
|
22
|
+
|
|
23
|
+
If you find zero vulnerabilities, you MUST return an empty array: {"vulnerabilities": []}.
|
|
24
|
+
|
|
25
|
+
USER: Analyze the following code snippet from the file '${filePath}'. The snippet starts at line ${startLine}:
|
|
26
|
+
|
|
27
|
+
${codeSnippet}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_identifyAreasOfInterest(content, patternFindings) {
|
|
31
|
+
const lines = content.split('\n');
|
|
32
|
+
const areas = new Set();
|
|
33
|
+
|
|
34
|
+
// Add lines from pattern findings
|
|
35
|
+
for (const finding of patternFindings) {
|
|
36
|
+
const lineNum = finding.line_number || 1;
|
|
37
|
+
areas.add(lineNum);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Look for function/method definitions and lines with interest keywords
|
|
41
|
+
for (let i = 0; i < lines.length; i++) {
|
|
42
|
+
const line = lines[i];
|
|
43
|
+
const lineLower = line.toLowerCase();
|
|
44
|
+
|
|
45
|
+
// Function/method definitions
|
|
46
|
+
if (/^\s*(def\s+|function\s+|class\s+|\w+\s*\([^)]*\)\s*{)/.test(line.trim())) {
|
|
47
|
+
areas.add(i + 1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Lines containing interest keywords
|
|
51
|
+
if (Array.from(this.interestKeywords).some(keyword => lineLower.includes(keyword))) {
|
|
52
|
+
areas.add(i + 1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Convert to ranges with context
|
|
57
|
+
const ranges = [];
|
|
58
|
+
const sortedAreas = Array.from(areas).sort((a, b) => a - b);
|
|
59
|
+
|
|
60
|
+
for (const lineNum of sortedAreas) {
|
|
61
|
+
const start = Math.max(1, lineNum - 10);
|
|
62
|
+
const end = Math.min(lines.length, lineNum + 10);
|
|
63
|
+
ranges.push([start, end]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Merge overlapping ranges
|
|
67
|
+
const mergedRanges = [];
|
|
68
|
+
for (const [start, end] of ranges) {
|
|
69
|
+
if (mergedRanges.length > 0 && start <= mergedRanges[mergedRanges.length - 1][1] + 10) {
|
|
70
|
+
// Extend previous range
|
|
71
|
+
const lastRange = mergedRanges[mergedRanges.length - 1];
|
|
72
|
+
mergedRanges[mergedRanges.length - 1] = [lastRange[0], Math.max(lastRange[1], end)];
|
|
73
|
+
} else {
|
|
74
|
+
mergedRanges.push([start, end]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return mergedRanges;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async callGrokApi(codeSnippet, filePath, startLine) {
|
|
82
|
+
try {
|
|
83
|
+
return await this._callBackendApi(codeSnippet, filePath, startLine);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (this.verbose) {
|
|
86
|
+
console.log(` ā LLM call failed, using mock: ${error.message}`);
|
|
87
|
+
}
|
|
88
|
+
return this._mockSnippetResponse(codeSnippet, filePath, startLine);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async _callBackendApi(codeSnippet, filePath, startLine) {
|
|
93
|
+
const payload = JSON.stringify({
|
|
94
|
+
codeSnippet,
|
|
95
|
+
filePath,
|
|
96
|
+
startLine
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const url = new URL(this.backendEndpoint);
|
|
100
|
+
url.pathname = '/api/analyze';
|
|
101
|
+
|
|
102
|
+
const options = {
|
|
103
|
+
hostname: url.hostname,
|
|
104
|
+
port: url.port || 443,
|
|
105
|
+
path: url.pathname,
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: {
|
|
108
|
+
'Content-Type': 'application/json',
|
|
109
|
+
'Content-Length': Buffer.byteLength(payload)
|
|
110
|
+
},
|
|
111
|
+
timeout: 12000 // 12 second timeout for CLI request
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
const req = https.request(options, (res) => {
|
|
116
|
+
let data = '';
|
|
117
|
+
|
|
118
|
+
res.on('data', (chunk) => {
|
|
119
|
+
data += chunk;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
res.on('end', () => {
|
|
123
|
+
try {
|
|
124
|
+
if (res.statusCode === 429) {
|
|
125
|
+
const errorData = JSON.parse(data);
|
|
126
|
+
throw new Error(`Rate limit exceeded: ${errorData.message}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (res.statusCode !== 200) {
|
|
130
|
+
throw new Error(`HTTP ${res.statusCode}: ${data}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const result = JSON.parse(data);
|
|
134
|
+
const content = result.data.choices[0].message.content;
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const parsedResponse = JSON.parse(content);
|
|
138
|
+
if (this.verbose) {
|
|
139
|
+
console.log(` ā
LLM returned ${parsedResponse.vulnerabilities?.length || 0} vulnerabilities`);
|
|
140
|
+
if (result.rateLimitRemaining !== undefined) {
|
|
141
|
+
console.log(` š Rate limit remaining: ${result.rateLimitRemaining}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
parsedResponse.__source = 'llm';
|
|
145
|
+
resolve(parsedResponse);
|
|
146
|
+
} catch (jsonError) {
|
|
147
|
+
const jsonStart = content.indexOf('{');
|
|
148
|
+
const jsonEnd = content.lastIndexOf('}') + 1;
|
|
149
|
+
if (jsonStart !== -1 && jsonEnd > jsonStart) {
|
|
150
|
+
const parsed = JSON.parse(content.substring(jsonStart, jsonEnd));
|
|
151
|
+
if (this.verbose) {
|
|
152
|
+
console.log(` ā
LLM returned ${parsed.vulnerabilities?.length || 0} vulnerabilities (extracted JSON)`);
|
|
153
|
+
}
|
|
154
|
+
parsed.__source = 'llm';
|
|
155
|
+
resolve(parsed);
|
|
156
|
+
} else {
|
|
157
|
+
resolve({ vulnerabilities: [] });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
reject(error);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
req.on('error', (error) => {
|
|
167
|
+
reject(error);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
req.on('timeout', () => {
|
|
171
|
+
req.destroy();
|
|
172
|
+
reject(new Error('Backend API request timeout'));
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
req.write(payload);
|
|
176
|
+
req.end();
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
_mockSnippetResponse(codeSnippet, filePath, startLine) {
|
|
182
|
+
const vulnerabilities = [];
|
|
183
|
+
const lines = codeSnippet.split('\n');
|
|
184
|
+
|
|
185
|
+
// Simple heuristic-based mock analysis
|
|
186
|
+
for (let i = 0; i < lines.length; i++) {
|
|
187
|
+
const line = lines[i];
|
|
188
|
+
const lineLower = line.toLowerCase();
|
|
189
|
+
const actualLineNum = startLine + i;
|
|
190
|
+
|
|
191
|
+
// Look for logging of potentially sensitive data
|
|
192
|
+
if (lineLower.includes('log') && ['email', 'user', 'password', 'token'].some(term => lineLower.includes(term))) {
|
|
193
|
+
vulnerabilities.push({
|
|
194
|
+
line_number: actualLineNum,
|
|
195
|
+
severity: 'Medium',
|
|
196
|
+
description: 'Potential logging of sensitive user data detected.',
|
|
197
|
+
suggestion: 'Consider logging only non-sensitive identifiers or hashing sensitive data before logging.'
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Look for insecure data transmission
|
|
202
|
+
if (lineLower.includes('http://') && ['api', 'send', 'post', 'request'].some(term => lineLower.includes(term))) {
|
|
203
|
+
vulnerabilities.push({
|
|
204
|
+
line_number: actualLineNum,
|
|
205
|
+
severity: 'High',
|
|
206
|
+
description: 'Insecure HTTP transmission of potentially sensitive data.',
|
|
207
|
+
suggestion: 'Use HTTPS instead of HTTP for all data transmission.'
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { vulnerabilities, __source: 'mock' };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async analyzeFile(filePath, content, patternFindings = []) {
|
|
216
|
+
const lines = content.split('\n');
|
|
217
|
+
const areasOfInterest = this._identifyAreasOfInterest(content, patternFindings);
|
|
218
|
+
const findings = [];
|
|
219
|
+
|
|
220
|
+
if (this.verbose && areasOfInterest.length > 0) {
|
|
221
|
+
console.log(` Found ${areasOfInterest.length} areas of interest for LLM analysis`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Analyze each area of interest
|
|
225
|
+
for (const [startLine, endLine] of areasOfInterest) {
|
|
226
|
+
// Extract snippet
|
|
227
|
+
const snippetLines = lines.slice(startLine - 1, endLine);
|
|
228
|
+
const snippet = snippetLines.join('\n');
|
|
229
|
+
|
|
230
|
+
// Skip very small snippets
|
|
231
|
+
if (snippet.trim().length < 50) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
// Call API with rate limiting
|
|
237
|
+
const grokResponse = await this.callGrokApi(snippet, filePath, startLine);
|
|
238
|
+
|
|
239
|
+
// Add delay to prevent rate limiting from free tier
|
|
240
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
241
|
+
|
|
242
|
+
// Process findings
|
|
243
|
+
for (const vuln of grokResponse.vulnerabilities || []) {
|
|
244
|
+
const finding = {
|
|
245
|
+
file_path: filePath,
|
|
246
|
+
line_number: vuln.line_number || startLine,
|
|
247
|
+
severity: vuln.severity || 'Medium',
|
|
248
|
+
finding_type: 'Context-Based Issue',
|
|
249
|
+
description: vuln.description || 'Privacy vulnerability detected',
|
|
250
|
+
code_snippet: this._getCodeSnippet(content, vuln.line_number || startLine),
|
|
251
|
+
suggestion: vuln.suggestion || 'Review and address the identified issue.',
|
|
252
|
+
source: grokResponse.__source || 'unknown'
|
|
253
|
+
};
|
|
254
|
+
findings.push(finding);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
} catch (error) {
|
|
258
|
+
// Continue with other snippets if one fails
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Remove duplicates based on line number and description
|
|
264
|
+
const seen = new Set();
|
|
265
|
+
const uniqueFindings = [];
|
|
266
|
+
for (const finding of findings) {
|
|
267
|
+
const key = `${finding.line_number}:${finding.description}`;
|
|
268
|
+
if (!seen.has(key)) {
|
|
269
|
+
seen.add(key);
|
|
270
|
+
uniqueFindings.push(finding);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return uniqueFindings;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
_getCodeSnippet(content, lineNumber) {
|
|
278
|
+
const lines = content.split('\n');
|
|
279
|
+
if (lineNumber >= 1 && lineNumber <= lines.length) {
|
|
280
|
+
return lines[lineNumber - 1].trim();
|
|
281
|
+
}
|
|
282
|
+
return 'Code snippet unavailable';
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
module.exports = LLMAnalyzer;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
class PatternScanner {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.patterns = this._initPatterns();
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
_initPatterns() {
|
|
7
|
+
return {
|
|
8
|
+
'aws_access_key': {
|
|
9
|
+
pattern: /\b(AKIA[0-9A-Z]{16})\b/g,
|
|
10
|
+
severity: 'Critical',
|
|
11
|
+
type: 'Hardcoded Secret',
|
|
12
|
+
description: 'AWS Access Key ID detected'
|
|
13
|
+
},
|
|
14
|
+
'aws_secret_key': {
|
|
15
|
+
pattern: /\b[A-Za-z0-9/+=]{40}\b/g,
|
|
16
|
+
severity: 'Critical',
|
|
17
|
+
type: 'Hardcoded Secret',
|
|
18
|
+
description: 'Potential AWS Secret Access Key detected'
|
|
19
|
+
},
|
|
20
|
+
'stripe_key': {
|
|
21
|
+
pattern: /\b(sk_live_[0-9a-zA-Z]{24}|pk_live_[0-9a-zA-Z]{24})\b/g,
|
|
22
|
+
severity: 'Critical',
|
|
23
|
+
type: 'Hardcoded Secret',
|
|
24
|
+
description: 'Stripe API key detected'
|
|
25
|
+
},
|
|
26
|
+
'private_key': {
|
|
27
|
+
pattern: /-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/g,
|
|
28
|
+
severity: 'Critical',
|
|
29
|
+
type: 'Hardcoded Secret',
|
|
30
|
+
description: 'Private key detected'
|
|
31
|
+
},
|
|
32
|
+
'high_entropy': {
|
|
33
|
+
pattern: /\b[A-Za-z0-9+/=]{32,}\b/g,
|
|
34
|
+
severity: 'Medium',
|
|
35
|
+
type: 'Hardcoded Secret',
|
|
36
|
+
description: 'High entropy string (potential secret)'
|
|
37
|
+
},
|
|
38
|
+
'email': {
|
|
39
|
+
pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/gi,
|
|
40
|
+
severity: 'Medium',
|
|
41
|
+
type: 'PII Detected',
|
|
42
|
+
description: 'Email address detected'
|
|
43
|
+
},
|
|
44
|
+
'phone': {
|
|
45
|
+
pattern: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b|\b\(\d{3}\)\s?\d{3}[-.]?\d{4}\b/g,
|
|
46
|
+
severity: 'Medium',
|
|
47
|
+
type: 'PII Detected',
|
|
48
|
+
description: 'Phone number detected'
|
|
49
|
+
},
|
|
50
|
+
'ip_address': {
|
|
51
|
+
pattern: /\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/g,
|
|
52
|
+
severity: 'Low',
|
|
53
|
+
type: 'PII Detected',
|
|
54
|
+
description: 'IP address detected'
|
|
55
|
+
},
|
|
56
|
+
'sensitive_keywords': {
|
|
57
|
+
pattern: /\b(password|secret|api_key|token|ssn|credit_card|credentials)\s*[=:]\s*["']?[^"'\s]+/gi,
|
|
58
|
+
severity: 'High',
|
|
59
|
+
type: 'Hardcoded Secret',
|
|
60
|
+
description: 'Sensitive keyword in assignment context'
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
_calculateEntropy(data) {
|
|
66
|
+
if (!data) return 0;
|
|
67
|
+
|
|
68
|
+
const counts = {};
|
|
69
|
+
for (const char of data) {
|
|
70
|
+
counts[char] = (counts[char] || 0) + 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let entropy = 0;
|
|
74
|
+
const length = data.length;
|
|
75
|
+
|
|
76
|
+
for (const count of Object.values(counts)) {
|
|
77
|
+
const probability = count / length;
|
|
78
|
+
entropy -= probability * Math.log2(probability);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return entropy;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
scanContent(filePath, content) {
|
|
85
|
+
const findings = [];
|
|
86
|
+
const lines = content.split('\n');
|
|
87
|
+
|
|
88
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
89
|
+
const line = lines[lineNum];
|
|
90
|
+
|
|
91
|
+
for (const [patternName, patternInfo] of Object.entries(this.patterns)) {
|
|
92
|
+
// Reset regex lastIndex for global patterns
|
|
93
|
+
patternInfo.pattern.lastIndex = 0;
|
|
94
|
+
|
|
95
|
+
let match;
|
|
96
|
+
while ((match = patternInfo.pattern.exec(line)) !== null) {
|
|
97
|
+
// Additional filtering for high entropy strings
|
|
98
|
+
if (patternName === 'high_entropy') {
|
|
99
|
+
const matchedText = match[0];
|
|
100
|
+
if (this._calculateEntropy(matchedText) < 4.0) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (matchedText.length < 20) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Skip common false positives for AWS secret pattern
|
|
109
|
+
if (patternName === 'aws_secret_key') {
|
|
110
|
+
const matchedText = match[0];
|
|
111
|
+
const falsePositives = ['example', 'dummy', 'test', 'fake'];
|
|
112
|
+
if (falsePositives.some(fp => matchedText.toLowerCase().includes(fp))) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const finding = {
|
|
118
|
+
file_path: filePath,
|
|
119
|
+
line_number: lineNum + 1,
|
|
120
|
+
severity: patternInfo.severity,
|
|
121
|
+
finding_type: patternInfo.type,
|
|
122
|
+
description: patternInfo.description,
|
|
123
|
+
code_snippet: line.trim()
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
findings.push(finding);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return findings;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = PatternScanner;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
class ReportGenerator {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.reportTime = new Date();
|
|
6
|
+
this.severityIcons = {
|
|
7
|
+
'Critical': 'šØ',
|
|
8
|
+
'High': 'š„',
|
|
9
|
+
'Medium': 'ā ļø',
|
|
10
|
+
'Low': 'šµ'
|
|
11
|
+
};
|
|
12
|
+
this.severityOrder = ['Critical', 'High', 'Medium', 'Low'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
_calculateGrade(findings) {
|
|
16
|
+
if (!findings.length) {
|
|
17
|
+
return 'A+';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const severityCounts = {};
|
|
21
|
+
this.severityOrder.forEach(severity => {
|
|
22
|
+
severityCounts[severity] = 0;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
for (const finding of findings) {
|
|
26
|
+
const severity = finding.severity || 'Low';
|
|
27
|
+
if (severityCounts.hasOwnProperty(severity)) {
|
|
28
|
+
severityCounts[severity]++;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Grading logic
|
|
33
|
+
if (severityCounts['Critical'] > 0) {
|
|
34
|
+
return 'F';
|
|
35
|
+
} else if (severityCounts['High'] > 3) {
|
|
36
|
+
return 'D';
|
|
37
|
+
} else if (severityCounts['High'] > 0) {
|
|
38
|
+
return 'C-';
|
|
39
|
+
} else if (severityCounts['Medium'] > 5) {
|
|
40
|
+
return 'B-';
|
|
41
|
+
} else if (severityCounts['Medium'] > 0) {
|
|
42
|
+
return 'B';
|
|
43
|
+
} else if (severityCounts['Low'] > 0) {
|
|
44
|
+
return 'A-';
|
|
45
|
+
} else {
|
|
46
|
+
return 'A+';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_groupFindingsBySeverity(findings) {
|
|
51
|
+
const groups = {};
|
|
52
|
+
this.severityOrder.forEach(severity => {
|
|
53
|
+
groups[severity] = [];
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
for (const finding of findings) {
|
|
57
|
+
const severity = finding.severity || 'Low';
|
|
58
|
+
if (groups.hasOwnProperty(severity)) {
|
|
59
|
+
groups[severity].push(finding);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return groups;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_getGradeColor(grade) {
|
|
67
|
+
const colors = {
|
|
68
|
+
'A+': 'š¢', 'A': 'š¢', 'A-': 'š¢',
|
|
69
|
+
'B+': 'š”', 'B': 'š”', 'B-': 'š”',
|
|
70
|
+
'C+': 'š ', 'C': 'š ', 'C-': 'š ',
|
|
71
|
+
'D': 'š“', 'F': 'š“'
|
|
72
|
+
};
|
|
73
|
+
return colors[grade] || 'āŖ';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
generateReport(scanDir, findings, fileCount) {
|
|
77
|
+
const reportLines = [];
|
|
78
|
+
|
|
79
|
+
// ASCII Art Header
|
|
80
|
+
reportLines.push(
|
|
81
|
+
'',
|
|
82
|
+
chalk.red('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'),
|
|
83
|
+
chalk.red('ā ā'),
|
|
84
|
+
chalk.red('ā āāā āāā āāāāāā āāāāāāāāāāā āāā āāāāāā āāāāāāā āāāāāāā āāāāāāā āāāāāāāā ā'),
|
|
85
|
+
chalk.red('ā āāā āāāāāāāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā'),
|
|
86
|
+
chalk.red('ā āāāāāāā āāāāāāāāāāāāāā āāāāāāā āāāāāāāāāāā āāā āāāāāā āāāāāāāāā ā'),
|
|
87
|
+
chalk.red('ā āāāāāāā āāāāāāāāāāāāāā āāāāāāā āāāāāāāāāāā āāā āāāāāā āāāāāāāāā ā'),
|
|
88
|
+
chalk.red('ā āāā āāāāāā āāāāāā āāā āāāāāā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā'),
|
|
89
|
+
chalk.red('ā āāā āāāāāā āāāāāā āāā āāāāāā āāā āāāāāāā āāāāāāā āāāāāāā āāāāāāāā ā'),
|
|
90
|
+
chalk.red('ā ā'),
|
|
91
|
+
chalk.red('ā š SHIFT-LEFT PRIVACY & COMPLIANCE SCANNER š ā'),
|
|
92
|
+
chalk.red('ā Powered by AI ⢠Built for Developers ā'),
|
|
93
|
+
chalk.red('ā ā'),
|
|
94
|
+
chalk.red('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'),
|
|
95
|
+
'',
|
|
96
|
+
'šÆ PRIVACY SCAN REPORT',
|
|
97
|
+
'ā'.repeat(80),
|
|
98
|
+
''
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Summary box
|
|
102
|
+
const severityCounts = {};
|
|
103
|
+
this.severityOrder.forEach(severity => {
|
|
104
|
+
severityCounts[severity] = 0;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
for (const finding of findings) {
|
|
108
|
+
const severity = finding.severity || 'Low';
|
|
109
|
+
if (severityCounts.hasOwnProperty(severity)) {
|
|
110
|
+
severityCounts[severity]++;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const grade = this._calculateGrade(findings);
|
|
115
|
+
const gradeColor = this._getGradeColor(grade);
|
|
116
|
+
|
|
117
|
+
// Count LLM vs Mock findings
|
|
118
|
+
const llmCount = findings.filter(f => f.source === 'llm').length;
|
|
119
|
+
const patternCount = findings.length - llmCount;
|
|
120
|
+
|
|
121
|
+
const timestamp = this.reportTime.toISOString().slice(0, 19).replace('T', ' ');
|
|
122
|
+
|
|
123
|
+
reportLines.push(
|
|
124
|
+
'āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā',
|
|
125
|
+
'ā š SCAN SUMMARY ā',
|
|
126
|
+
'āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤',
|
|
127
|
+
`ā š Directory: ${scanDir.padEnd(60)} ā`,
|
|
128
|
+
`ā ā° Timestamp: ${timestamp.padEnd(60)} ā`,
|
|
129
|
+
`ā š Files Scanned: ${fileCount.toString().padEnd(57)} ā`,
|
|
130
|
+
`ā š Total Issues: ${findings.length.toString().padEnd(58)} ā`,
|
|
131
|
+
`ā š Privacy Grade: ${gradeColor}${grade.padEnd(56)} ā`,
|
|
132
|
+
'ā ā',
|
|
133
|
+
'ā šÆ Detection Methods: ā',
|
|
134
|
+
`ā ⢠Pattern-based: ${patternCount} issues ā`,
|
|
135
|
+
`ā ⢠AI-powered: ${llmCount} issues (Grok 4 Fast) ā`,
|
|
136
|
+
'ā ā',
|
|
137
|
+
'ā š Severity Breakdown: ā',
|
|
138
|
+
`ā šØ Critical: ${severityCounts['Critical'].toString().padEnd(10)} š„ High: ${severityCounts['High'].toString().padEnd(14)} ā`,
|
|
139
|
+
`ā ā ļø Medium: ${severityCounts['Medium'].toString().padEnd(11)} šµ Low: ${severityCounts['Low'].toString().padEnd(15)} ā`,
|
|
140
|
+
'āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā',
|
|
141
|
+
''
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Findings breakdown
|
|
145
|
+
if (!findings.length) {
|
|
146
|
+
reportLines.push(
|
|
147
|
+
'āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā',
|
|
148
|
+
'ā š CONGRATULATIONS! š ā',
|
|
149
|
+
'ā ā',
|
|
150
|
+
'ā ⨠No privacy issues detected! ⨠ā',
|
|
151
|
+
'ā ā',
|
|
152
|
+
'ā Your codebase follows privacy best practices! ā',
|
|
153
|
+
'ā ā',
|
|
154
|
+
'āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā',
|
|
155
|
+
''
|
|
156
|
+
);
|
|
157
|
+
} else {
|
|
158
|
+
const groupedFindings = this._groupFindingsBySeverity(findings);
|
|
159
|
+
|
|
160
|
+
for (const severity of this.severityOrder) {
|
|
161
|
+
const severityFindings = groupedFindings[severity];
|
|
162
|
+
if (!severityFindings.length) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const icon = this.severityIcons[severity];
|
|
167
|
+
const colorBar = 'ā'.repeat(Math.min(severityFindings.length, 40));
|
|
168
|
+
|
|
169
|
+
reportLines.push(
|
|
170
|
+
'',
|
|
171
|
+
`ā${'ā'.repeat(77)}ā®`,
|
|
172
|
+
`ā ${icon} ${severity.toUpperCase()} SEVERITY ISSUES (${severityFindings.length} found)`.padEnd(76) + 'ā',
|
|
173
|
+
`ā ${colorBar}`.padEnd(76) + 'ā',
|
|
174
|
+
`ā°${'ā'.repeat(77)}āÆ`,
|
|
175
|
+
''
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
for (let i = 0; i < severityFindings.length; i++) {
|
|
179
|
+
const finding = severityFindings[i];
|
|
180
|
+
const sourceBadge = finding.source === 'llm' ? 'š¤ AI' : 'š Pattern';
|
|
181
|
+
|
|
182
|
+
reportLines.push(
|
|
183
|
+
`āāā Issue #${i + 1} āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā`,
|
|
184
|
+
`ā ${icon} ${finding.description || 'Privacy issue detected'}`,
|
|
185
|
+
'ā',
|
|
186
|
+
`ā š Location: ${finding.file_path || 'Unknown'}:${finding.line_number || 0}`,
|
|
187
|
+
`ā šØ Severity: ${finding.severity || 'Unknown'}`,
|
|
188
|
+
`ā š Detection: ${sourceBadge}`,
|
|
189
|
+
'ā',
|
|
190
|
+
'ā š¾ Code:',
|
|
191
|
+
`ā ${(finding.line_number || 0).toString().padStart(3)} ā ${finding.code_snippet || 'N/A'}`
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
if (finding.suggestion) {
|
|
195
|
+
reportLines.push(
|
|
196
|
+
'ā',
|
|
197
|
+
'ā š” Suggestion:',
|
|
198
|
+
`ā ${finding.suggestion}`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
reportLines.push(
|
|
203
|
+
'āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā',
|
|
204
|
+
''
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Footer
|
|
211
|
+
reportLines.push(
|
|
212
|
+
'āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā',
|
|
213
|
+
'ā š GET STARTED ā',
|
|
214
|
+
'ā ā',
|
|
215
|
+
'ā š Documentation: https://kafkacode.dev/docs ā',
|
|
216
|
+
'ā š GitHub: https://github.com/kafkacode/privacy-scanner ā',
|
|
217
|
+
'ā š¬ Support: https://discord.gg/kafkacode ā',
|
|
218
|
+
'ā š¦ Follow: @KafkaCodeDev ā',
|
|
219
|
+
'ā ā',
|
|
220
|
+
'ā š”ļø Keep your code secure, keep your users safe! š”ļø ā',
|
|
221
|
+
'āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā',
|
|
222
|
+
''
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
return reportLines.join('\n');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
module.exports = ReportGenerator;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Command } = require('commander');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const FileScanner = require('./FileScanner');
|
|
7
|
+
const AnalysisEngine = require('./AnalysisEngine');
|
|
8
|
+
const ReportGenerator = require('./ReportGenerator');
|
|
9
|
+
|
|
10
|
+
const program = new Command();
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.name('kafkacode')
|
|
14
|
+
.description('KafkaCode - Privacy and Compliance Scanner')
|
|
15
|
+
.version('1.2.0');
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.command('scan')
|
|
19
|
+
.description('Scan a directory for privacy issues')
|
|
20
|
+
.argument('<directory>', 'Path to the source code directory to scan')
|
|
21
|
+
.option('-v, --verbose', 'Print verbose progress updates during the scan')
|
|
22
|
+
.action(async (directory, options) => {
|
|
23
|
+
await runScan(directory, options);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
async function runScan(directory, options = {}) {
|
|
27
|
+
const verbose = options.verbose || false;
|
|
28
|
+
|
|
29
|
+
// Validate directory
|
|
30
|
+
if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) {
|
|
31
|
+
console.error(`Error: Directory '${directory}' does not exist or is not a directory.`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (verbose) {
|
|
36
|
+
console.log('š Starting KafkaCode privacy scan...');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Initialize components
|
|
41
|
+
const fileScanner = new FileScanner(directory);
|
|
42
|
+
const analysisEngine = new AnalysisEngine(verbose);
|
|
43
|
+
const reportGenerator = new ReportGenerator();
|
|
44
|
+
|
|
45
|
+
// Scan for files
|
|
46
|
+
if (verbose) {
|
|
47
|
+
console.log('š Discovering source code files...');
|
|
48
|
+
}
|
|
49
|
+
const files = fileScanner.scanFiles();
|
|
50
|
+
|
|
51
|
+
if (!files.length) {
|
|
52
|
+
console.log('No source code files found to analyze.');
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (verbose) {
|
|
57
|
+
console.log(`Found ${files.length} files to analyze`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Analyze files
|
|
61
|
+
if (verbose) {
|
|
62
|
+
console.log('š Performing privacy analysis...');
|
|
63
|
+
}
|
|
64
|
+
const findings = await analysisEngine.analyzeFiles(files);
|
|
65
|
+
|
|
66
|
+
// Generate and display report
|
|
67
|
+
const report = reportGenerator.generateReport(directory, findings, files.length);
|
|
68
|
+
console.log(report);
|
|
69
|
+
|
|
70
|
+
// Return appropriate exit code
|
|
71
|
+
process.exit(findings.length > 0 ? 1 : 0);
|
|
72
|
+
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error.name === 'AbortError' || error.message.includes('aborted')) {
|
|
75
|
+
console.log('\nā ļø Scan interrupted by user.');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
} else {
|
|
78
|
+
console.error(`ā Error during scan: ${error.message}`);
|
|
79
|
+
if (verbose) {
|
|
80
|
+
console.error(error.stack);
|
|
81
|
+
}
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle SIGINT (Ctrl+C)
|
|
88
|
+
process.on('SIGINT', () => {
|
|
89
|
+
console.log('\nā ļø Scan interrupted by user.');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
program.parse();
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const FileScanner = require('./FileScanner');
|
|
2
|
+
const PatternScanner = require('./PatternScanner');
|
|
3
|
+
const LLMAnalyzer = require('./LLMAnalyzer');
|
|
4
|
+
const AnalysisEngine = require('./AnalysisEngine');
|
|
5
|
+
const ReportGenerator = require('./ReportGenerator');
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
FileScanner,
|
|
9
|
+
PatternScanner,
|
|
10
|
+
LLMAnalyzer,
|
|
11
|
+
AnalysisEngine,
|
|
12
|
+
ReportGenerator
|
|
13
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kafkacode",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "AI-powered privacy and compliance scanner by KafkaLabs - identify PII leaks, secrets, and compliance violations",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"kafkacode": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "node build.js",
|
|
11
|
+
"prepublishOnly": "npm run build",
|
|
12
|
+
"test": "node test/test.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"privacy",
|
|
16
|
+
"compliance",
|
|
17
|
+
"security",
|
|
18
|
+
"scanner",
|
|
19
|
+
"static-analysis",
|
|
20
|
+
"pii",
|
|
21
|
+
"gdpr",
|
|
22
|
+
"ccpa",
|
|
23
|
+
"secret-detection",
|
|
24
|
+
"code-analysis",
|
|
25
|
+
"privacy-scanner",
|
|
26
|
+
"ai-powered",
|
|
27
|
+
"shift-left",
|
|
28
|
+
"cli-tool",
|
|
29
|
+
"security-scanner",
|
|
30
|
+
"vulnerability-scanner",
|
|
31
|
+
"kafkalabs"
|
|
32
|
+
],
|
|
33
|
+
"author": "KafkaLabs <contact@kafkalabs.com>",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"commander": "^9.4.1",
|
|
37
|
+
"minimatch": "^9.0.3",
|
|
38
|
+
"chalk": "^4.1.2"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=14.0.0"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist/",
|
|
45
|
+
"README.md",
|
|
46
|
+
"LICENSE",
|
|
47
|
+
"CHANGELOG.md"
|
|
48
|
+
],
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "https://github.com/nikhil-kapu/KafkacodeFnpm.git"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://kafkalabs.com/kafka-code",
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/nikhil-kapu/KafkacodeFnpm/issues"
|
|
56
|
+
}
|
|
57
|
+
}
|