kodingo-cli 1.1.8 → 1.1.10
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.
|
@@ -66,6 +66,9 @@ const SUPPORTED_EXTENSIONS = [
|
|
|
66
66
|
const IGNORED_DIRS = [
|
|
67
67
|
"node_modules", ".git", ".kortex", "dist", "build",
|
|
68
68
|
"out", ".next", "coverage", "__pycache__", "vendor",
|
|
69
|
+
".venv", "venv", "env", ".env", "site-packages",
|
|
70
|
+
"virtualenv", ".tox", "eggs", ".eggs", "wheels",
|
|
71
|
+
"target", ".gradle", ".idea", ".vscode", "bin", "obj",
|
|
69
72
|
];
|
|
70
73
|
const MAX_SYMBOLS = 200;
|
|
71
74
|
const BATCH_SIZE = 5;
|
|
@@ -18,7 +18,7 @@ const graph_builder_1 = require("../utils/graph-builder");
|
|
|
18
18
|
const security_code_analyzer_1 = require("../utils/security-code-analyzer");
|
|
19
19
|
const context_assembler_1 = require("../utils/context-assembler");
|
|
20
20
|
const ast_parser_1 = require("../utils/ast-parser");
|
|
21
|
-
const IGNORED_DIRS = new Set(["node_modules", ".git", ".kortex", "dist", "build", "vendor", "out", ".next"]);
|
|
21
|
+
const IGNORED_DIRS = new Set(["node_modules", ".git", ".kortex", "dist", "build", "vendor", "out", ".next", ".venv", "venv", "env", "site-packages", "virtualenv", ".tox", "target", ".gradle", ".idea", ".vscode", "bin", "obj"]);
|
|
22
22
|
const SUPPORTED_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".php", ".java", ".kt", ".rb", ".c", ".cpp", ".html", ".phtml", ".twig", ".blade.php"];
|
|
23
23
|
function walkFiles(dir) {
|
|
24
24
|
const results = [];
|
|
@@ -130,7 +130,37 @@ async function securityScanCommand(options = {}) {
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
catch { }
|
|
133
|
-
const
|
|
133
|
+
const allCandidates = (0, security_code_analyzer_1.analyzeForSecurityCandidates)(repoRoot, graph, symbols, dynamicRules);
|
|
134
|
+
// Fetch already-confirmed open findings so we don't re-scan them —
|
|
135
|
+
// they stay visible in the dashboard as open until fixed, but we use
|
|
136
|
+
// our 30-candidate budget on NEW issues each week.
|
|
137
|
+
let confirmedFingerprints = new Set();
|
|
138
|
+
try {
|
|
139
|
+
const existingRes = await fetch(`${apiUrl}/security/findings?repo=${encodeURIComponent(repoName)}`, {
|
|
140
|
+
headers: { "X-Kodingo-Token": token },
|
|
141
|
+
});
|
|
142
|
+
if (existingRes.ok) {
|
|
143
|
+
const { findings: existingFindings } = await existingRes.json();
|
|
144
|
+
existingFindings.forEach((f) => {
|
|
145
|
+
confirmedFingerprints.add(`${f.finding_type}:${f.endpoint ?? f.symbol ?? ""}:${f.vulnerability_id ?? ""}`);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch { }
|
|
150
|
+
// Filter out already-confirmed findings, then sort by severity and cap at 30
|
|
151
|
+
const SEVERITY_ORDER = { critical: 0, high: 1, moderate: 2, low: 3 };
|
|
152
|
+
const newCandidates = allCandidates.filter(c => {
|
|
153
|
+
const fp = `${c.patternType}:${c.symbol ?? c.file}:${c.file}`;
|
|
154
|
+
return !confirmedFingerprints.has(fp);
|
|
155
|
+
});
|
|
156
|
+
const candidates = newCandidates
|
|
157
|
+
.sort((a, b) => (SEVERITY_ORDER[a.severity ?? 'low'] ?? 3) - (SEVERITY_ORDER[b.severity ?? 'low'] ?? 3))
|
|
158
|
+
.slice(0, 30);
|
|
159
|
+
if (!options.silent) {
|
|
160
|
+
if (allCandidates.length > 0) {
|
|
161
|
+
console.log(` Found ${allCandidates.length} candidates (${allCandidates.length - newCandidates.length} already tracked as open) — reviewing ${Math.min(candidates.length, 30)} new ones\n`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
134
164
|
if (candidates.length === 0) {
|
|
135
165
|
if (!options.silent)
|
|
136
166
|
console.log(" ✔ No suspicious patterns detected.");
|
|
@@ -154,7 +154,7 @@ function parseComposerLock(repoRoot, filePath) {
|
|
|
154
154
|
}
|
|
155
155
|
return entries;
|
|
156
156
|
}
|
|
157
|
-
const IGNORED_DIRS = new Set(["node_modules", ".git", ".kortex", "dist", "build", "vendor", "out", ".next"]);
|
|
157
|
+
const IGNORED_DIRS = new Set(["node_modules", ".git", ".kortex", "dist", "build", "vendor", "out", ".next", ".venv", "venv", "env", "site-packages", "virtualenv", ".tox", "target", ".gradle", ".idea", ".vscode", "bin", "obj"]);
|
|
158
158
|
function findManifestFiles(repoRoot) {
|
|
159
159
|
const result = { packageJson: [], packageLock: [], composerJson: [], composerLock: [] };
|
|
160
160
|
function walk(dir) {
|
|
@@ -70,6 +70,8 @@ const TS_JS_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
|
70
70
|
const IGNORED_DIRS = [
|
|
71
71
|
"node_modules", ".git", ".kortex", "dist", "build",
|
|
72
72
|
"out", ".next", "coverage", "__pycache__", "vendor",
|
|
73
|
+
".venv", "venv", "env", "site-packages", "virtualenv",
|
|
74
|
+
".tox", "target", ".gradle", ".idea", ".vscode", "bin", "obj",
|
|
73
75
|
];
|
|
74
76
|
function walkFiles(dir) {
|
|
75
77
|
const results = [];
|
|
@@ -14,10 +14,16 @@
|
|
|
14
14
|
* server for Layer 2 — an LLM judgment with full visibility, which is what
|
|
15
15
|
* actually adjudicates real risk vs false positive.
|
|
16
16
|
*/
|
|
17
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
18
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
|
+
};
|
|
17
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
21
|
exports.findMissingAuthCandidatesLocal = findMissingAuthCandidatesLocal;
|
|
19
22
|
exports.analyzeForSecurityCandidates = analyzeForSecurityCandidates;
|
|
20
23
|
const context_assembler_1 = require("./context-assembler");
|
|
24
|
+
const fs_1 = __importDefault(require("fs"));
|
|
25
|
+
const path_1 = __importDefault(require("path"));
|
|
26
|
+
const child_process_1 = require("child_process");
|
|
21
27
|
const code_pattern_scanner_1 = require("./code-pattern-scanner");
|
|
22
28
|
const ROUTE_REGISTRATION_METHOD_NAMES = new Set(["get", "post", "put", "delete", "patch", "addhook", "all"]);
|
|
23
29
|
const AUTH_FUNCTION_HINTS = [
|
|
@@ -80,6 +86,77 @@ function analyzeForSecurityCandidates(repoRoot, graph, symbols, dynamicRules = [
|
|
|
80
86
|
const candidates = [];
|
|
81
87
|
// ── Code pattern findings — static + dynamic rules ────────────────────────
|
|
82
88
|
const patternFindings = (0, code_pattern_scanner_1.scanAllSymbolsForPatterns)(symbols);
|
|
89
|
+
// ── Tier 1: Fast file-level scan (no LLM, runs on all files) ─────────────
|
|
90
|
+
// Catches vulnerabilities in procedural code outside named functions.
|
|
91
|
+
// Only high-confidence patterns are included — no ambiguous matches.
|
|
92
|
+
// File selection is smart: recently changed files first, then unscanned,
|
|
93
|
+
// capped at 100 files per run to stay fast and avoid timeouts.
|
|
94
|
+
const HIGH_CONFIDENCE_PATTERNS = new Set(["sql_injection", "command_injection", "hardcoded_secret", "path_traversal"]);
|
|
95
|
+
const SUPPORTED_EXTS = new Set([".php", ".py", ".go", ".rs", ".java", ".kt", ".rb", ".c", ".cpp", ".html", ".phtml", ".js", ".ts", ".jsx", ".tsx"]);
|
|
96
|
+
const EXT_TO_LANG = {
|
|
97
|
+
".php": "php", ".phtml": "php", ".py": "python", ".go": "go", ".rs": "rust",
|
|
98
|
+
".java": "java", ".kt": "kotlin", ".rb": "ruby", ".c": "c", ".cpp": "cpp",
|
|
99
|
+
".html": "html", ".js": "javascript", ".ts": "typescript", ".jsx": "javascript", ".tsx": "typescript"
|
|
100
|
+
};
|
|
101
|
+
const IGNORED_DIRS = new Set(["node_modules", ".git", "dist", "build", "vendor", "__pycache__", ".next", "out", "coverage", ".venv", "venv", "env", "site-packages", "virtualenv", ".tox", "target", ".gradle", ".idea", ".vscode", "bin", "obj"]);
|
|
102
|
+
// Get recently changed files from git (last 30 days) — scan these first
|
|
103
|
+
let priorityFiles = [];
|
|
104
|
+
try {
|
|
105
|
+
const gitOut = (0, child_process_1.execSync)("git log --since=30.days --name-only --pretty=format: --diff-filter=AM", { cwd: repoRoot, timeout: 5000 }).toString();
|
|
106
|
+
priorityFiles = gitOut.split("\n").filter(f => f.trim() && SUPPORTED_EXTS.has(path_1.default.extname(f)))
|
|
107
|
+
.map(f => path_1.default.join(repoRoot, f)).filter(f => fs_1.default.existsSync(f)).slice(0, 50);
|
|
108
|
+
}
|
|
109
|
+
catch { }
|
|
110
|
+
// Walk repo for remaining files — skip already-in-priority
|
|
111
|
+
const prioritySet = new Set(priorityFiles);
|
|
112
|
+
const allWalked = [];
|
|
113
|
+
const walk = (dir) => {
|
|
114
|
+
try {
|
|
115
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
116
|
+
const full = path_1.default.join(dir, entry.name);
|
|
117
|
+
if (entry.isDirectory() && !IGNORED_DIRS.has(entry.name))
|
|
118
|
+
walk(full);
|
|
119
|
+
else if (entry.isFile() && SUPPORTED_EXTS.has(path_1.default.extname(entry.name)) && !prioritySet.has(full)) {
|
|
120
|
+
allWalked.push(full);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch { }
|
|
125
|
+
};
|
|
126
|
+
walk(repoRoot);
|
|
127
|
+
// Priority files first, then remaining up to cap of 100 total
|
|
128
|
+
const filesToScan = [...priorityFiles, ...allWalked].slice(0, 100);
|
|
129
|
+
const alreadyScannedFiles = new Set(symbols.map(s => s.file));
|
|
130
|
+
for (const filePath of filesToScan) {
|
|
131
|
+
if (alreadyScannedFiles.has(filePath))
|
|
132
|
+
continue; // already covered by symbol scan
|
|
133
|
+
try {
|
|
134
|
+
const source = fs_1.default.readFileSync(filePath, "utf-8");
|
|
135
|
+
const ext = path_1.default.extname(filePath);
|
|
136
|
+
const language = EXT_TO_LANG[ext] ?? "javascript";
|
|
137
|
+
const syntheticSymbol = {
|
|
138
|
+
name: path_1.default.basename(filePath),
|
|
139
|
+
file: filePath,
|
|
140
|
+
startLine: 1,
|
|
141
|
+
body: source.slice(0, 10000),
|
|
142
|
+
fingerprint: `file:${filePath}`,
|
|
143
|
+
calls: [],
|
|
144
|
+
routes: [],
|
|
145
|
+
imports: [],
|
|
146
|
+
language,
|
|
147
|
+
type: "function",
|
|
148
|
+
endLine: source.split("\n").length,
|
|
149
|
+
};
|
|
150
|
+
const fileFindings = (0, code_pattern_scanner_1.scanSymbolForPatterns)(syntheticSymbol, language)
|
|
151
|
+
.filter(f => HIGH_CONFIDENCE_PATTERNS.has(f.patternType)); // Tier 1: high-confidence only
|
|
152
|
+
for (const f of fileFindings) {
|
|
153
|
+
patternFindings.push(f);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
83
160
|
// Apply dynamic rules generated from OSV vulnerability data
|
|
84
161
|
for (const sym of symbols) {
|
|
85
162
|
const language = sym.language ?? "unknown";
|