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 candidates = (0, security_code_analyzer_1.analyzeForSecurityCandidates)(repoRoot, graph, symbols, dynamicRules);
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodingo-cli",
3
- "version": "1.1.8",
3
+ "version": "1.1.10",
4
4
  "description": "Kodingo CLI",
5
5
  "license": "MIT",
6
6
  "private": false,