kodingo-cli 1.1.1 → 1.1.2

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.
@@ -115,7 +115,22 @@ async function securityScanCommand(options = {}) {
115
115
  }
116
116
  catch { }
117
117
  }
118
- const candidates = (0, security_code_analyzer_1.analyzeForSecurityCandidates)(repoRoot, graph, symbols);
118
+ // Fetch dynamically generated rules from OSV-driven rule generation
119
+ let dynamicRules = [];
120
+ try {
121
+ const rulesRes = await fetch(`${apiUrl}/security/rules`, {
122
+ headers: { "X-Kodingo-Token": token },
123
+ });
124
+ if (rulesRes.ok) {
125
+ const rulesData = await rulesRes.json();
126
+ dynamicRules = rulesData.rules ?? [];
127
+ if (!options.silent && dynamicRules.length > 0) {
128
+ console.log(` Loaded ${dynamicRules.length} dynamically generated detection rule${dynamicRules.length === 1 ? "" : "s"} from OSV intelligence\n`);
129
+ }
130
+ }
131
+ }
132
+ catch { }
133
+ const candidates = (0, security_code_analyzer_1.analyzeForSecurityCandidates)(repoRoot, graph, symbols, dynamicRules);
119
134
  if (candidates.length === 0) {
120
135
  if (!options.silent)
121
136
  console.log(" ✔ No suspicious patterns detected.");
@@ -93,6 +93,109 @@ const RULES = [
93
93
  regex: /\b(fetch|axios\.get|axios\.post|request)\s*\(\s*[^)]*\+\s*\w+/,
94
94
  description: "Outbound HTTP request built via concatenation with a variable — potential SSRF if that variable is user-controlled.",
95
95
  },
96
+ // ── Insecure deserialization ───────────────────────────────────────────────
97
+ {
98
+ type: "insecure_deserialization",
99
+ severity: "critical",
100
+ regex: /\b(unserialize|pickle\.loads|deserialize)\s*\(/,
101
+ description: "Unvalidated data passed to a deserialization function — can enable remote code execution if input is attacker-controlled.",
102
+ },
103
+ {
104
+ type: "insecure_deserialization",
105
+ severity: "critical",
106
+ regex: /yaml\.load\s*\([^,)]+\)(?!.*Loader)/,
107
+ description: "yaml.load() without a safe Loader — enables arbitrary code execution via crafted YAML input.",
108
+ languages: ["python"],
109
+ },
110
+ // ── XXE injection ──────────────────────────────────────────────────────────
111
+ {
112
+ type: "xxe_injection",
113
+ severity: "high",
114
+ regex: /resolve_entities\s*=\s*True|load_dtd\s*=\s*True/,
115
+ description: "XML parser configured with external entity resolution or DTD loading enabled — XXE injection vulnerability.",
116
+ languages: ["python"],
117
+ },
118
+ {
119
+ type: "xxe_injection",
120
+ severity: "high",
121
+ regex: /libxml_disable_entity_loader\s*\(\s*false\s*\)/,
122
+ description: "PHP XML external entity loading explicitly enabled — XXE injection vulnerability.",
123
+ languages: ["php"],
124
+ },
125
+ // ── Open redirect ─────────────────────────────────────────────────────────
126
+ {
127
+ type: "open_redirect",
128
+ severity: "high",
129
+ regex: /\bres\.redirect\s*\([^)]*(?:req\.|params\.|query\.)/,
130
+ description: "Redirect target built from user-supplied request data — potential open redirect enabling phishing attacks.",
131
+ },
132
+ {
133
+ type: "open_redirect",
134
+ severity: "high",
135
+ regex: /header\s*\(\s*['"]Location.*(?:\$_GET|\$_POST|\$_REQUEST)/,
136
+ description: "Location header set from user-supplied input — open redirect vulnerability.",
137
+ languages: ["php"],
138
+ },
139
+ // ── Mass assignment ───────────────────────────────────────────────────────
140
+ {
141
+ type: "mass_assignment",
142
+ severity: "high",
143
+ regex: /Object\.assign\s*\([^,)]+,\s*req\.body/,
144
+ description: "User-supplied request body merged directly into an object via Object.assign — potential mass assignment.",
145
+ },
146
+ {
147
+ type: "mass_assignment",
148
+ severity: "high",
149
+ regex: /\$request->all\(\)|\$request->input\(\)/,
150
+ description: "All user input bound directly to model without allowlist — mass assignment vulnerability in Laravel/PHP.",
151
+ languages: ["php"],
152
+ },
153
+ // ── Prototype pollution ───────────────────────────────────────────────────
154
+ {
155
+ type: "prototype_pollution",
156
+ severity: "critical",
157
+ regex: /\[['"]__proto__['"]\]|__proto__\s*=/,
158
+ description: "Prototype pollution — modification of __proto__ through user-controlled input can corrupt the global object prototype.",
159
+ },
160
+ {
161
+ type: "prototype_pollution",
162
+ severity: "critical",
163
+ regex: /constructor\.prototype\s*=/,
164
+ description: "Direct modification of constructor.prototype — potential prototype pollution vulnerability.",
165
+ },
166
+ // ── CORS misconfiguration ─────────────────────────────────────────────────
167
+ {
168
+ type: "cors_misconfiguration",
169
+ severity: "high",
170
+ regex: /['"]Access-Control-Allow-Origin['"]\s*[,:]\s*['"]\*['"]/,
171
+ description: "Wildcard CORS policy — allows any origin to make cross-origin requests to this endpoint.",
172
+ },
173
+ {
174
+ type: "cors_misconfiguration",
175
+ severity: "moderate",
176
+ regex: /origin\s*[:=]\s*req\.headers(?:\.origin|\.get\s*\(\s*['"]origin)/,
177
+ description: "CORS origin reflected directly from request headers without validation — allows attacker-controlled origins.",
178
+ },
179
+ // ── Timing attacks ────────────────────────────────────────────────────────
180
+ {
181
+ type: "timing_attack",
182
+ severity: "moderate",
183
+ regex: /(?:token|secret|password|apiKey|api_key)\s*===\s*(?:req|request|input|provided)/,
184
+ description: "Non-constant-time comparison in a security-sensitive context — vulnerable to timing attacks that can leak secret values.",
185
+ },
186
+ // ── Insecure session management ───────────────────────────────────────────
187
+ {
188
+ type: "insecure_session",
189
+ severity: "high",
190
+ regex: /(?:session|sessionId|session_id)\s*[:=][^;\n]*Math\.random/,
191
+ description: "Session ID generated using Math.random() — not cryptographically secure, session IDs are predictable.",
192
+ },
193
+ {
194
+ type: "insecure_session",
195
+ severity: "moderate",
196
+ regex: /res\.cookie\s*\([^)]+\)(?!.*httpOnly)(?!.*secure)/,
197
+ description: "Cookie set without httpOnly or Secure flags — session cookie may be accessible to JavaScript or transmitted over HTTP.",
198
+ },
96
199
  ];
97
200
  /**
98
201
  * Scans a single symbol's body against all pattern rules. Returns every match —
@@ -73,12 +73,39 @@ function findMissingAuthCandidatesLocal(graph, symbolsByFingerprint) {
73
73
  /**
74
74
  * Runs both Layer 1 detectors and assembles full code context for every
75
75
  * flagged candidate — ready to send to the server for Layer 2 LLM judgment.
76
+ * Dynamic rules from OSV-driven generation are applied alongside static rules.
76
77
  */
77
- function analyzeForSecurityCandidates(repoRoot, graph, symbols) {
78
+ function analyzeForSecurityCandidates(repoRoot, graph, symbols, dynamicRules = []) {
78
79
  const symbolsByFingerprint = new Map(symbols.map(s => [s.fingerprint, s]));
79
80
  const candidates = [];
80
- // ── Code pattern findings ────────────────────────────────────────────────
81
+ // ── Code pattern findings — static + dynamic rules ────────────────────────
81
82
  const patternFindings = (0, code_pattern_scanner_1.scanAllSymbolsForPatterns)(symbols);
83
+ // Apply dynamic rules generated from OSV vulnerability data
84
+ for (const sym of symbols) {
85
+ const language = sym.language ?? "unknown";
86
+ for (const rule of dynamicRules) {
87
+ if (rule.languages && !rule.languages.includes(language))
88
+ continue;
89
+ try {
90
+ const regex = new RegExp(rule.regexPattern);
91
+ const match = sym.body.match(regex);
92
+ if (match && match.index !== undefined) {
93
+ patternFindings.push({
94
+ patternType: rule.patternType,
95
+ symbol: sym.name,
96
+ file: sym.file,
97
+ startLine: sym.startLine,
98
+ severity: rule.severity,
99
+ snippet: sym.body.slice(Math.max(0, match.index - 20), match.index + 100).trim(),
100
+ description: rule.description,
101
+ });
102
+ }
103
+ }
104
+ catch {
105
+ continue;
106
+ }
107
+ }
108
+ }
82
109
  for (const finding of patternFindings) {
83
110
  const sym = symbols.find(s => s.name === finding.symbol && s.file === finding.file);
84
111
  const fingerprint = sym?.fingerprint ?? "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodingo-cli",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Kodingo CLI",
5
5
  "license": "MIT",
6
6
  "private": false,