kirograph 1.2.2 → 1.2.3

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.
@@ -128,7 +128,7 @@ process.on("uncaughtException", (err) => {
128
128
  process.exit(1);
129
129
  });
130
130
  const program = new import_commander.Command();
131
- program.name("kirograph").description("Semantic code knowledge graph for Kiro").version("1.2.2").addHelpCommand(true).hook("preAction", (thisCommand) => {
131
+ program.name("kirograph").description("Semantic code knowledge graph for Kiro").version("1.2.3").addHelpCommand(true).hook("preAction", (thisCommand) => {
132
132
  const name = thisCommand.name();
133
133
  if (name === "init") (0, import_banner.printBanner)();
134
134
  });
@@ -8,5 +8,8 @@ fix: "/* TODO: use parameterized query */ $DB.query('?', [$B])"
8
8
  rule:
9
9
  any:
10
10
  - pattern: $DB.query($A + $B)
11
+ - pattern: $DB.query($A + $B, $$$REST)
11
12
  - pattern: $DB.execute($A + $B)
13
+ - pattern: $DB.execute($A + $B, $$$REST)
12
14
  - pattern: $DB.run($A + $B)
15
+ - pattern: $DB.run($A + $B, $$$REST)
@@ -8,6 +8,10 @@ fix: "/* TODO: replace with parameterized query */"
8
8
  rule:
9
9
  any:
10
10
  - pattern: $DB.query(`$$$`)
11
+ - pattern: $DB.query(`$$$`, $$$REST)
11
12
  - pattern: $DB.execute(`$$$`)
13
+ - pattern: $DB.execute(`$$$`, $$$REST)
12
14
  - pattern: $DB.run(`$$$`)
15
+ - pattern: $DB.run(`$$$`, $$$REST)
13
16
  - pattern: $DB.exec(`$$$`)
17
+ - pattern: $DB.exec(`$$$`, $$$REST)
@@ -190,16 +190,6 @@ class DataFlowAnalyzer {
190
190
  JOIN nodes n2 ON n2.id = e.target
191
191
  WHERE e.kind = 'calls'
192
192
  AND n2.name IN ('query', 'execute', 'exec', 'raw', 'runQuery', 'executeQuery', 'runSql', 'executeSql')
193
- AND (
194
- n.name LIKE '%handle%'
195
- OR n.name LIKE '%controller%'
196
- OR n.name LIKE '%route%'
197
- OR n.name LIKE '%request%'
198
- OR n.name LIKE '%req%'
199
- OR n.name LIKE '%endpoint%'
200
- OR n.name LIKE '%action%'
201
- OR n.name LIKE '%handler%'
202
- )
203
193
  AND n.file_path != ''
204
194
  `);
205
195
  const sanitizeCallers = new Set(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/security/data-flows.ts"],
4
- "sourcesContent": ["/**\n * KiroGraph SAST-lite: Dangerous Data Flow Detection\n *\n * Detects dangerous patterns by analysing call edges in the graph database.\n * Uses symbol names and callee chains rather than full AST traversal.\n */\n\nimport type { GraphDatabase } from '../db/database';\nimport type { OwaspCategory } from './owasp';\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface DataFlowFinding {\n type: 'sql-injection' | 'path-traversal' | 'xss' | 'dangerous-eval' | 'unsafe-deserialize' | 'hardcoded-crypto';\n severity: 'critical' | 'high' | 'medium';\n owaspCategory: OwaspCategory;\n filePath: string;\n line: number;\n symbol: string;\n description: string;\n recommendation: string;\n}\n\n// \u2500\u2500 Internal row type returned by the DB queries \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface CallEdgeRow {\n caller: string;\n file_path: string;\n start_line: number;\n callee: string;\n}\n\n// \u2500\u2500 hasPatternsData helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction hasPatternsData(rawDb: any): boolean {\n try {\n const result = rawDb.get(\"SELECT COUNT(*) as cnt FROM pattern_matches\") as { cnt: number } | undefined;\n return (result?.cnt ?? 0) > 0;\n } catch {\n // Table doesn't exist (enablePatterns never ran)\n return false;\n }\n}\n\n// \u2500\u2500 DataFlowAnalyzer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class DataFlowAnalyzer {\n constructor(private readonly db: GraphDatabase) {}\n\n async analyze(): Promise<DataFlowFinding[]> {\n const rawDb = (this.db as any).getRawDb();\n\n // Always run existing SQL heuristics (unchanged behavior)\n const sqlFindings = [\n ...this._detectDangerousEval(rawDb),\n ...this._detectUnsafeDeserialize(rawDb),\n ...this._detectHardcodedCrypto(rawDb),\n ...this._detectSqlInjection(rawDb),\n ...this._detectPathTraversal(rawDb),\n ];\n\n // When pattern_matches has data, merge AST findings (more precise)\n if (hasPatternsData(rawDb)) {\n const astFindings = this.analyzeFromPatternMatches(rawDb);\n // Deduplicate: AST entry wins over SQL entry at same (filePath, line)\n const astKeys = new Set(astFindings.map(f => `${f.filePath}:${f.line}`));\n const filteredSql = sqlFindings.filter(f => !astKeys.has(`${f.filePath}:${f.line}`));\n return [...astFindings, ...filteredSql];\n }\n\n return sqlFindings;\n }\n\n private analyzeFromPatternMatches(rawDb: any): DataFlowFinding[] {\n const rows: Array<{\n file_path: string;\n pattern_id: string;\n line: number;\n col: number;\n match_text: string;\n severity: string;\n owasp_category: string;\n language: string;\n }> = rawDb.all('SELECT file_path, pattern_id, line, col, match_text, severity, owasp_category, language FROM pattern_matches');\n\n return rows.map(row => {\n // Map pattern_id prefix to DataFlowFinding type\n const type = this._patternIdToType(row.pattern_id);\n // Look up nearest enclosing symbol\n const symbol = this._findEnclosingSymbol(rawDb, row.file_path, row.line);\n return {\n type,\n severity: row.severity as DataFlowFinding['severity'],\n owaspCategory: row.owasp_category as DataFlowFinding['owaspCategory'],\n filePath: row.file_path,\n line: row.line,\n symbol: symbol ?? row.pattern_id,\n description: `[AST pattern: ${row.pattern_id}] ${row.match_text.slice(0, 100)}`,\n recommendation: this._getRecommendation(type),\n };\n });\n }\n\n private _patternIdToType(patternId: string): DataFlowFinding['type'] {\n if (patternId.startsWith('sql-injection')) return 'sql-injection';\n if (patternId.startsWith('dangerous-eval') || patternId.startsWith('dangerous-exec')) return 'dangerous-eval';\n if (patternId.startsWith('path-traversal')) return 'path-traversal';\n if (patternId.startsWith('prototype-pollution')) return 'unsafe-deserialize';\n if (patternId.startsWith('weak-crypto')) return 'hardcoded-crypto';\n return 'dangerous-eval'; // fallback\n }\n\n private _findEnclosingSymbol(rawDb: any, filePath: string, line: number): string | null {\n const row = rawDb.get(\n `SELECT name FROM nodes WHERE file_path = ? AND start_line <= ? AND end_line >= ? AND kind IN ('function', 'method', 'class') ORDER BY (end_line - start_line) ASC LIMIT 1`,\n [filePath, line, line]\n ) as { name: string } | undefined;\n return row?.name ?? null;\n }\n\n private _getRecommendation(type: DataFlowFinding['type']): string {\n const recs: Record<DataFlowFinding['type'], string> = {\n 'sql-injection': 'Use parameterized queries. Never concatenate user input into SQL strings.',\n 'dangerous-eval': 'Remove eval()/exec(). Use structured alternatives.',\n 'path-traversal': 'Use path.resolve() and verify result is within an allowed base directory.',\n 'unsafe-deserialize': 'Validate object keys before merge. Avoid untrusted deserialization.',\n 'hardcoded-crypto': 'Use SHA-256+ for hashing. Use bcrypt/argon2 for passwords.',\n 'xss': 'Sanitize output. Use a trusted template engine with auto-escaping.',\n };\n return recs[type] ?? 'Review and fix this security issue.';\n }\n\n // \u2500\u2500 Detection: dangerous eval / exec \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private _detectDangerousEval(rawDb: any): DataFlowFinding[] {\n const rows: CallEdgeRow[] = rawDb.all(`\n SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee\n FROM edges e\n JOIN nodes n ON n.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'calls'\n AND n2.name IN ('eval', 'Function', 'execSync', 'exec', 'spawn', 'spawnSync', 'execFileSync', 'execFile')\n AND n.file_path != ''\n `);\n\n return rows.map((row) => ({\n type: 'dangerous-eval' as const,\n severity: 'critical' as const,\n owaspCategory: 'A03' as const,\n filePath: row.file_path,\n line: row.start_line,\n symbol: row.caller,\n description: `Function '${row.caller}' calls '${row.callee}', which can execute arbitrary code or OS commands.`,\n recommendation: `Avoid '${row.callee}'. Use structured APIs or allowlists for dynamic execution. Validate and sanitize all inputs.`,\n }));\n }\n\n // \u2500\u2500 Detection: unsafe deserialization \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private _detectUnsafeDeserialize(rawDb: any): DataFlowFinding[] {\n const rows: CallEdgeRow[] = rawDb.all(`\n SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee\n FROM edges e\n JOIN nodes n ON n.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'calls'\n AND (\n n2.name IN ('unserialize', 'deserialize', 'fromJson', 'loads')\n OR n2.name LIKE '%pickle%'\n OR n2.name LIKE '%marshal%'\n OR n2.name = 'yaml.load'\n )\n AND n.file_path != ''\n `);\n\n return rows.map((row) => ({\n type: 'unsafe-deserialize' as const,\n severity: 'high' as const,\n owaspCategory: 'A08' as const,\n filePath: row.file_path,\n line: row.start_line,\n symbol: row.caller,\n description: `Function '${row.caller}' calls '${row.callee}', which may deserialize untrusted data unsafely.`,\n recommendation: `Validate data before deserialization. Use safe alternatives (e.g. yaml.safe_load, JSON schema validation). Never deserialize data from untrusted sources.`,\n }));\n }\n\n // \u2500\u2500 Detection: hardcoded weak crypto \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private _detectHardcodedCrypto(rawDb: any): DataFlowFinding[] {\n const rows: CallEdgeRow[] = rawDb.all(`\n SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee\n FROM edges e\n JOIN nodes n ON n.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'calls'\n AND (\n n2.name LIKE '%md5%'\n OR n2.name LIKE '%sha1%'\n OR n2.name LIKE '%des%'\n OR n2.name LIKE '%rc4%'\n OR n2.name LIKE '%blowfish%'\n )\n AND (\n n.name LIKE '%crypto%'\n OR n.name LIKE '%hash%'\n OR n.name LIKE '%cipher%'\n OR n.name LIKE '%encrypt%'\n OR n.name LIKE '%digest%'\n OR n.file_path LIKE '%crypto%'\n OR n.file_path LIKE '%hash%'\n OR n.file_path LIKE '%cipher%'\n OR n.file_path LIKE '%auth%'\n )\n AND n.file_path != ''\n `);\n\n return rows.map((row) => ({\n type: 'hardcoded-crypto' as const,\n severity: 'medium' as const,\n owaspCategory: 'A02' as const,\n filePath: row.file_path,\n line: row.start_line,\n symbol: row.caller,\n description: `Function '${row.caller}' uses weak or deprecated cryptographic algorithm '${row.callee}'.`,\n recommendation: `Replace with a strong algorithm: SHA-256 or SHA-3 for hashing, AES-256-GCM for symmetric encryption. Avoid MD5 and SHA-1 for security purposes.`,\n }));\n }\n\n // \u2500\u2500 Detection: SQL injection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private _detectSqlInjection(rawDb: any): DataFlowFinding[] {\n // Find functions that:\n // (a) call a known DB query function\n // (b) AND whose name suggests user-input handling (controller/route/request context)\n const rows: CallEdgeRow[] = rawDb.all(`\n SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee\n FROM edges e\n JOIN nodes n ON n.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'calls'\n AND n2.name IN ('query', 'execute', 'exec', 'raw', 'runQuery', 'executeQuery', 'runSql', 'executeSql')\n AND (\n n.name LIKE '%handle%'\n OR n.name LIKE '%controller%'\n OR n.name LIKE '%route%'\n OR n.name LIKE '%request%'\n OR n.name LIKE '%req%'\n OR n.name LIKE '%endpoint%'\n OR n.name LIKE '%action%'\n OR n.name LIKE '%handler%'\n )\n AND n.file_path != ''\n `);\n\n // Only report entries that do NOT also have a sanitize/escape call in the same scope\n const sanitizeCallers: Set<string> = new Set<string>(\n (rawDb.all(`\n SELECT DISTINCT n.id\n FROM edges e\n JOIN nodes n ON n.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'calls'\n AND (\n n2.name LIKE '%sanitize%'\n OR n2.name LIKE '%escape%'\n OR n2.name LIKE '%parameteriz%'\n OR n2.name LIKE '%prepared%'\n OR n2.name LIKE '%placeholder%'\n )\n `) as Array<{ id: string }>).map((r) => r.id),\n );\n\n // Cross-reference: look up caller node IDs to filter sanitized callers\n const callerIds: Map<string, string> = new Map(\n (rawDb.all(`\n SELECT id, name FROM nodes WHERE kind IN ('function', 'method', 'arrow_function')\n `) as Array<{ id: string; name: string }>).map((r) => [r.name, r.id]),\n );\n\n return rows\n .filter((row) => {\n const callerId = callerIds.get(row.caller);\n return !callerId || !sanitizeCallers.has(callerId);\n })\n .map((row) => ({\n type: 'sql-injection' as const,\n severity: 'critical' as const,\n owaspCategory: 'A03' as const,\n filePath: row.file_path,\n line: row.start_line,\n symbol: row.caller,\n description: `Controller/handler '${row.caller}' calls database function '${row.callee}' without apparent input sanitization.`,\n recommendation: `Use parameterized queries or prepared statements. Never concatenate user input into SQL strings. Consider an ORM with query builders.`,\n }));\n }\n\n // \u2500\u2500 Detection: path traversal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private _detectPathTraversal(rawDb: any): DataFlowFinding[] {\n // Find functions that call file I/O operations and look like they handle requests.\n // Exclusions:\n // - Internal analysis/infrastructure paths (mcp, resolution, security, extraction, graph, architecture)\n // - 'open' is excluded from file ops because KiroGraph.open() (DB) triggers false positives\n // - Class names ending in 'Handler' or 'Server' are excluded (too broad \u2014 use more specific patterns)\n const rows: CallEdgeRow[] = rawDb.all(`\n SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee\n FROM edges e\n JOIN nodes n ON n.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'calls'\n AND n2.name IN ('readFile', 'readFileSync', 'writeFile', 'writeFileSync', 'createReadStream', 'createWriteStream', 'openSync', 'unlink', 'unlinkSync')\n AND (\n n.name LIKE '%controller%'\n OR n.name LIKE '%route%'\n OR n.name LIKE '%request%'\n OR n.name LIKE '%download%'\n OR n.name LIKE '%upload%'\n OR n.name LIKE '%get%file%'\n OR n.name LIKE '%fetch%file%'\n OR n.name LIKE '%serve%file%'\n OR n.name LIKE '%handle%file%'\n )\n AND n.file_path != ''\n AND n.file_path NOT LIKE '%/mcp/%'\n AND n.file_path NOT LIKE '%/resolution/%'\n AND n.file_path NOT LIKE '%/security/%'\n AND n.file_path NOT LIKE '%/extraction/%'\n AND n.file_path NOT LIKE '%/graph/%'\n AND n.file_path NOT LIKE '%/architecture/%'\n AND n.file_path NOT LIKE '%/frameworks/%'\n AND n.file_path NOT LIKE '%/bridges/%'\n `);\n\n return rows.map((row) => ({\n type: 'path-traversal' as const,\n severity: 'high' as const,\n owaspCategory: 'A01' as const,\n filePath: row.file_path,\n line: row.start_line,\n symbol: row.caller,\n description: `Handler '${row.caller}' calls file operation '${row.callee}'. Path may be influenced by user input, enabling directory traversal.`,\n recommendation: `Validate and normalize file paths. Use path.resolve() and verify the result is inside an allowed base directory. Reject paths containing '..'.`,\n }));\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * KiroGraph SAST-lite: Dangerous Data Flow Detection\n *\n * Detects dangerous patterns by analysing call edges in the graph database.\n * Uses symbol names and callee chains rather than full AST traversal.\n */\n\nimport type { GraphDatabase } from '../db/database';\nimport type { OwaspCategory } from './owasp';\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface DataFlowFinding {\n type: 'sql-injection' | 'path-traversal' | 'xss' | 'dangerous-eval' | 'unsafe-deserialize' | 'hardcoded-crypto';\n severity: 'critical' | 'high' | 'medium';\n owaspCategory: OwaspCategory;\n filePath: string;\n line: number;\n symbol: string;\n description: string;\n recommendation: string;\n}\n\n// \u2500\u2500 Internal row type returned by the DB queries \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface CallEdgeRow {\n caller: string;\n file_path: string;\n start_line: number;\n callee: string;\n}\n\n// \u2500\u2500 hasPatternsData helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction hasPatternsData(rawDb: any): boolean {\n try {\n const result = rawDb.get(\"SELECT COUNT(*) as cnt FROM pattern_matches\") as { cnt: number } | undefined;\n return (result?.cnt ?? 0) > 0;\n } catch {\n // Table doesn't exist (enablePatterns never ran)\n return false;\n }\n}\n\n// \u2500\u2500 DataFlowAnalyzer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class DataFlowAnalyzer {\n constructor(private readonly db: GraphDatabase) {}\n\n async analyze(): Promise<DataFlowFinding[]> {\n const rawDb = (this.db as any).getRawDb();\n\n // Always run existing SQL heuristics (unchanged behavior)\n const sqlFindings = [\n ...this._detectDangerousEval(rawDb),\n ...this._detectUnsafeDeserialize(rawDb),\n ...this._detectHardcodedCrypto(rawDb),\n ...this._detectSqlInjection(rawDb),\n ...this._detectPathTraversal(rawDb),\n ];\n\n // When pattern_matches has data, merge AST findings (more precise)\n if (hasPatternsData(rawDb)) {\n const astFindings = this.analyzeFromPatternMatches(rawDb);\n // Deduplicate: AST entry wins over SQL entry at same (filePath, line)\n const astKeys = new Set(astFindings.map(f => `${f.filePath}:${f.line}`));\n const filteredSql = sqlFindings.filter(f => !astKeys.has(`${f.filePath}:${f.line}`));\n return [...astFindings, ...filteredSql];\n }\n\n return sqlFindings;\n }\n\n private analyzeFromPatternMatches(rawDb: any): DataFlowFinding[] {\n const rows: Array<{\n file_path: string;\n pattern_id: string;\n line: number;\n col: number;\n match_text: string;\n severity: string;\n owasp_category: string;\n language: string;\n }> = rawDb.all('SELECT file_path, pattern_id, line, col, match_text, severity, owasp_category, language FROM pattern_matches');\n\n return rows.map(row => {\n // Map pattern_id prefix to DataFlowFinding type\n const type = this._patternIdToType(row.pattern_id);\n // Look up nearest enclosing symbol\n const symbol = this._findEnclosingSymbol(rawDb, row.file_path, row.line);\n return {\n type,\n severity: row.severity as DataFlowFinding['severity'],\n owaspCategory: row.owasp_category as DataFlowFinding['owaspCategory'],\n filePath: row.file_path,\n line: row.line,\n symbol: symbol ?? row.pattern_id,\n description: `[AST pattern: ${row.pattern_id}] ${row.match_text.slice(0, 100)}`,\n recommendation: this._getRecommendation(type),\n };\n });\n }\n\n private _patternIdToType(patternId: string): DataFlowFinding['type'] {\n if (patternId.startsWith('sql-injection')) return 'sql-injection';\n if (patternId.startsWith('dangerous-eval') || patternId.startsWith('dangerous-exec')) return 'dangerous-eval';\n if (patternId.startsWith('path-traversal')) return 'path-traversal';\n if (patternId.startsWith('prototype-pollution')) return 'unsafe-deserialize';\n if (patternId.startsWith('weak-crypto')) return 'hardcoded-crypto';\n return 'dangerous-eval'; // fallback\n }\n\n private _findEnclosingSymbol(rawDb: any, filePath: string, line: number): string | null {\n const row = rawDb.get(\n `SELECT name FROM nodes WHERE file_path = ? AND start_line <= ? AND end_line >= ? AND kind IN ('function', 'method', 'class') ORDER BY (end_line - start_line) ASC LIMIT 1`,\n [filePath, line, line]\n ) as { name: string } | undefined;\n return row?.name ?? null;\n }\n\n private _getRecommendation(type: DataFlowFinding['type']): string {\n const recs: Record<DataFlowFinding['type'], string> = {\n 'sql-injection': 'Use parameterized queries. Never concatenate user input into SQL strings.',\n 'dangerous-eval': 'Remove eval()/exec(). Use structured alternatives.',\n 'path-traversal': 'Use path.resolve() and verify result is within an allowed base directory.',\n 'unsafe-deserialize': 'Validate object keys before merge. Avoid untrusted deserialization.',\n 'hardcoded-crypto': 'Use SHA-256+ for hashing. Use bcrypt/argon2 for passwords.',\n 'xss': 'Sanitize output. Use a trusted template engine with auto-escaping.',\n };\n return recs[type] ?? 'Review and fix this security issue.';\n }\n\n // \u2500\u2500 Detection: dangerous eval / exec \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private _detectDangerousEval(rawDb: any): DataFlowFinding[] {\n const rows: CallEdgeRow[] = rawDb.all(`\n SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee\n FROM edges e\n JOIN nodes n ON n.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'calls'\n AND n2.name IN ('eval', 'Function', 'execSync', 'exec', 'spawn', 'spawnSync', 'execFileSync', 'execFile')\n AND n.file_path != ''\n `);\n\n return rows.map((row) => ({\n type: 'dangerous-eval' as const,\n severity: 'critical' as const,\n owaspCategory: 'A03' as const,\n filePath: row.file_path,\n line: row.start_line,\n symbol: row.caller,\n description: `Function '${row.caller}' calls '${row.callee}', which can execute arbitrary code or OS commands.`,\n recommendation: `Avoid '${row.callee}'. Use structured APIs or allowlists for dynamic execution. Validate and sanitize all inputs.`,\n }));\n }\n\n // \u2500\u2500 Detection: unsafe deserialization \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private _detectUnsafeDeserialize(rawDb: any): DataFlowFinding[] {\n const rows: CallEdgeRow[] = rawDb.all(`\n SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee\n FROM edges e\n JOIN nodes n ON n.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'calls'\n AND (\n n2.name IN ('unserialize', 'deserialize', 'fromJson', 'loads')\n OR n2.name LIKE '%pickle%'\n OR n2.name LIKE '%marshal%'\n OR n2.name = 'yaml.load'\n )\n AND n.file_path != ''\n `);\n\n return rows.map((row) => ({\n type: 'unsafe-deserialize' as const,\n severity: 'high' as const,\n owaspCategory: 'A08' as const,\n filePath: row.file_path,\n line: row.start_line,\n symbol: row.caller,\n description: `Function '${row.caller}' calls '${row.callee}', which may deserialize untrusted data unsafely.`,\n recommendation: `Validate data before deserialization. Use safe alternatives (e.g. yaml.safe_load, JSON schema validation). Never deserialize data from untrusted sources.`,\n }));\n }\n\n // \u2500\u2500 Detection: hardcoded weak crypto \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private _detectHardcodedCrypto(rawDb: any): DataFlowFinding[] {\n const rows: CallEdgeRow[] = rawDb.all(`\n SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee\n FROM edges e\n JOIN nodes n ON n.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'calls'\n AND (\n n2.name LIKE '%md5%'\n OR n2.name LIKE '%sha1%'\n OR n2.name LIKE '%des%'\n OR n2.name LIKE '%rc4%'\n OR n2.name LIKE '%blowfish%'\n )\n AND (\n n.name LIKE '%crypto%'\n OR n.name LIKE '%hash%'\n OR n.name LIKE '%cipher%'\n OR n.name LIKE '%encrypt%'\n OR n.name LIKE '%digest%'\n OR n.file_path LIKE '%crypto%'\n OR n.file_path LIKE '%hash%'\n OR n.file_path LIKE '%cipher%'\n OR n.file_path LIKE '%auth%'\n )\n AND n.file_path != ''\n `);\n\n return rows.map((row) => ({\n type: 'hardcoded-crypto' as const,\n severity: 'medium' as const,\n owaspCategory: 'A02' as const,\n filePath: row.file_path,\n line: row.start_line,\n symbol: row.caller,\n description: `Function '${row.caller}' uses weak or deprecated cryptographic algorithm '${row.callee}'.`,\n recommendation: `Replace with a strong algorithm: SHA-256 or SHA-3 for hashing, AES-256-GCM for symmetric encryption. Avoid MD5 and SHA-1 for security purposes.`,\n }));\n }\n\n // \u2500\u2500 Detection: SQL injection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private _detectSqlInjection(rawDb: any): DataFlowFinding[] {\n // Find every function that calls a known raw-DB-query function. Used to\n // require the caller's own name to look like a controller/route/handler\n // (issue #39 follow-up: verified against OWASP Juice Shop that this\n // missed both of its best-known SQL injections \u2014 `login()` and\n // `searchProducts()` call `sequelize.query()` directly, zero hops away,\n // but neither name matched the old allowlist). The callee name is\n // already the specific signal here (a handful of literal raw-query\n // method names); gating on the caller's name on top of that was a leaky\n // proxy for \"receives user input\" that excluded real vulnerable code\n // sitting in repository/service/route-closure functions with ordinary\n // names, so it's dropped \u2014 the sanitize/escape filter below still keeps\n // properly-parameterized callers out of the results.\n const rows: CallEdgeRow[] = rawDb.all(`\n SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee\n FROM edges e\n JOIN nodes n ON n.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'calls'\n AND n2.name IN ('query', 'execute', 'exec', 'raw', 'runQuery', 'executeQuery', 'runSql', 'executeSql')\n AND n.file_path != ''\n `);\n\n // Only report entries that do NOT also have a sanitize/escape call in the same scope\n const sanitizeCallers: Set<string> = new Set<string>(\n (rawDb.all(`\n SELECT DISTINCT n.id\n FROM edges e\n JOIN nodes n ON n.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'calls'\n AND (\n n2.name LIKE '%sanitize%'\n OR n2.name LIKE '%escape%'\n OR n2.name LIKE '%parameteriz%'\n OR n2.name LIKE '%prepared%'\n OR n2.name LIKE '%placeholder%'\n )\n `) as Array<{ id: string }>).map((r) => r.id),\n );\n\n // Cross-reference: look up caller node IDs to filter sanitized callers\n const callerIds: Map<string, string> = new Map(\n (rawDb.all(`\n SELECT id, name FROM nodes WHERE kind IN ('function', 'method', 'arrow_function')\n `) as Array<{ id: string; name: string }>).map((r) => [r.name, r.id]),\n );\n\n return rows\n .filter((row) => {\n const callerId = callerIds.get(row.caller);\n return !callerId || !sanitizeCallers.has(callerId);\n })\n .map((row) => ({\n type: 'sql-injection' as const,\n severity: 'critical' as const,\n owaspCategory: 'A03' as const,\n filePath: row.file_path,\n line: row.start_line,\n symbol: row.caller,\n description: `Controller/handler '${row.caller}' calls database function '${row.callee}' without apparent input sanitization.`,\n recommendation: `Use parameterized queries or prepared statements. Never concatenate user input into SQL strings. Consider an ORM with query builders.`,\n }));\n }\n\n // \u2500\u2500 Detection: path traversal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private _detectPathTraversal(rawDb: any): DataFlowFinding[] {\n // Find functions that call file I/O operations and look like they handle requests.\n // Exclusions:\n // - Internal analysis/infrastructure paths (mcp, resolution, security, extraction, graph, architecture)\n // - 'open' is excluded from file ops because KiroGraph.open() (DB) triggers false positives\n // - Class names ending in 'Handler' or 'Server' are excluded (too broad \u2014 use more specific patterns)\n const rows: CallEdgeRow[] = rawDb.all(`\n SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee\n FROM edges e\n JOIN nodes n ON n.id = e.source\n JOIN nodes n2 ON n2.id = e.target\n WHERE e.kind = 'calls'\n AND n2.name IN ('readFile', 'readFileSync', 'writeFile', 'writeFileSync', 'createReadStream', 'createWriteStream', 'openSync', 'unlink', 'unlinkSync')\n AND (\n n.name LIKE '%controller%'\n OR n.name LIKE '%route%'\n OR n.name LIKE '%request%'\n OR n.name LIKE '%download%'\n OR n.name LIKE '%upload%'\n OR n.name LIKE '%get%file%'\n OR n.name LIKE '%fetch%file%'\n OR n.name LIKE '%serve%file%'\n OR n.name LIKE '%handle%file%'\n )\n AND n.file_path != ''\n AND n.file_path NOT LIKE '%/mcp/%'\n AND n.file_path NOT LIKE '%/resolution/%'\n AND n.file_path NOT LIKE '%/security/%'\n AND n.file_path NOT LIKE '%/extraction/%'\n AND n.file_path NOT LIKE '%/graph/%'\n AND n.file_path NOT LIKE '%/architecture/%'\n AND n.file_path NOT LIKE '%/frameworks/%'\n AND n.file_path NOT LIKE '%/bridges/%'\n `);\n\n return rows.map((row) => ({\n type: 'path-traversal' as const,\n severity: 'high' as const,\n owaspCategory: 'A01' as const,\n filePath: row.file_path,\n line: row.start_line,\n symbol: row.caller,\n description: `Handler '${row.caller}' calls file operation '${row.callee}'. Path may be influenced by user input, enabling directory traversal.`,\n recommendation: `Validate and normalize file paths. Use path.resolve() and verify the result is inside an allowed base directory. Reject paths containing '..'.`,\n }));\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kirograph",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Semantic code knowledge graph for Kiro: fewer tool calls, instant symbol lookups, 100% local.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",