kirograph 0.17.0 → 0.19.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/LICENSE +1 -1
- package/README.md +103 -1822
- package/dist/architecture/layers/dart.js +112 -0
- package/dist/architecture/layers/dart.js.map +7 -0
- package/dist/architecture/layers/index.js +3 -1
- package/dist/architecture/layers/index.js.map +2 -2
- package/dist/bin/banner.js +74 -6
- package/dist/bin/banner.js.map +2 -2
- package/dist/bin/commands/attack-surface.js +157 -0
- package/dist/bin/commands/attack-surface.js.map +7 -0
- package/dist/bin/commands/benchmark.js +206 -0
- package/dist/bin/commands/benchmark.js.map +7 -0
- package/dist/bin/commands/budget.js +70 -0
- package/dist/bin/commands/budget.js.map +7 -0
- package/dist/bin/commands/communities.js +77 -0
- package/dist/bin/commands/communities.js.map +7 -0
- package/dist/bin/commands/dep-confusion.js +119 -0
- package/dist/bin/commands/dep-confusion.js.map +7 -0
- package/dist/bin/commands/export.js +304 -10
- package/dist/bin/commands/export.js.map +2 -2
- package/dist/bin/commands/flows.js +97 -0
- package/dist/bin/commands/flows.js.map +7 -0
- package/dist/bin/commands/help.js +382 -101
- package/dist/bin/commands/help.js.map +2 -2
- package/dist/bin/commands/install.js +90 -8
- package/dist/bin/commands/install.js.map +2 -2
- package/dist/bin/commands/licenses.js +201 -0
- package/dist/bin/commands/licenses.js.map +7 -0
- package/dist/bin/commands/reachability.js +160 -0
- package/dist/bin/commands/reachability.js.map +7 -0
- package/dist/bin/commands/read.js +100 -0
- package/dist/bin/commands/read.js.map +7 -0
- package/dist/bin/commands/refactor.js +116 -0
- package/dist/bin/commands/refactor.js.map +7 -0
- package/dist/bin/commands/remediation.js +133 -0
- package/dist/bin/commands/remediation.js.map +7 -0
- package/dist/bin/commands/sbom.js +76 -0
- package/dist/bin/commands/sbom.js.map +7 -0
- package/dist/bin/commands/security-ci-report.js +267 -0
- package/dist/bin/commands/security-ci-report.js.map +7 -0
- package/dist/bin/commands/security-export.js +1349 -0
- package/dist/bin/commands/security-export.js.map +7 -0
- package/dist/bin/commands/security-flows.js +126 -0
- package/dist/bin/commands/security-flows.js.map +7 -0
- package/dist/bin/commands/security-secrets.js +170 -0
- package/dist/bin/commands/security-secrets.js.map +7 -0
- package/dist/bin/commands/security.js +193 -0
- package/dist/bin/commands/security.js.map +7 -0
- package/dist/bin/commands/staleness.js +144 -0
- package/dist/bin/commands/staleness.js.map +7 -0
- package/dist/bin/commands/status.js +102 -2
- package/dist/bin/commands/status.js.map +2 -2
- package/dist/bin/commands/supply-chain.js +138 -0
- package/dist/bin/commands/supply-chain.js.map +7 -0
- package/dist/bin/commands/uninit.js +109 -60
- package/dist/bin/commands/uninit.js.map +2 -2
- package/dist/bin/commands/vex.js +76 -0
- package/dist/bin/commands/vex.js.map +7 -0
- package/dist/bin/commands/vuln-suppress.js +95 -0
- package/dist/bin/commands/vuln-suppress.js.map +7 -0
- package/dist/bin/commands/vulns.js +382 -0
- package/dist/bin/commands/vulns.js.map +7 -0
- package/dist/bin/installer/auto-detect.js +125 -0
- package/dist/bin/installer/auto-detect.js.map +7 -0
- package/dist/bin/installer/cli-agent.js +28 -5
- package/dist/bin/installer/cli-agent.js.map +2 -2
- package/dist/bin/installer/common.js +47 -0
- package/dist/bin/installer/common.js.map +2 -2
- package/dist/bin/installer/config-prompt.js +12 -1
- package/dist/bin/installer/config-prompt.js.map +2 -2
- package/dist/bin/installer/detect.js +309 -0
- package/dist/bin/installer/detect.js.map +7 -0
- package/dist/bin/installer/index.js +9 -1
- package/dist/bin/installer/index.js.map +2 -2
- package/dist/bin/installer/instructions.js +195 -3
- package/dist/bin/installer/instructions.js.map +2 -2
- package/dist/bin/installer/steering.js +407 -0
- package/dist/bin/installer/steering.js.map +2 -2
- package/dist/bin/installer/targets/aider.js +3 -3
- package/dist/bin/installer/targets/aider.js.map +2 -2
- package/dist/bin/installer/targets/amp.js +3 -3
- package/dist/bin/installer/targets/amp.js.map +2 -2
- package/dist/bin/installer/targets/antigravity.js +35 -6
- package/dist/bin/installer/targets/antigravity.js.map +2 -2
- package/dist/bin/installer/targets/augment.js +3 -3
- package/dist/bin/installer/targets/augment.js.map +2 -2
- package/dist/bin/installer/targets/claude.js +49 -5
- package/dist/bin/installer/targets/claude.js.map +2 -2
- package/dist/bin/installer/targets/cline.js +32 -6
- package/dist/bin/installer/targets/cline.js.map +2 -2
- package/dist/bin/installer/targets/codex.js +64 -12
- package/dist/bin/installer/targets/codex.js.map +2 -2
- package/dist/bin/installer/targets/continue.js +2 -2
- package/dist/bin/installer/targets/continue.js.map +2 -2
- package/dist/bin/installer/targets/copilot-cli.js +101 -0
- package/dist/bin/installer/targets/copilot-cli.js.map +7 -0
- package/dist/bin/installer/targets/copilot.js +31 -5
- package/dist/bin/installer/targets/copilot.js.map +2 -2
- package/dist/bin/installer/targets/cursor.js +4 -4
- package/dist/bin/installer/targets/cursor.js.map +2 -2
- package/dist/bin/installer/targets/devin.js +2 -2
- package/dist/bin/installer/targets/devin.js.map +2 -2
- package/dist/bin/installer/targets/gemini-cli.js +2 -2
- package/dist/bin/installer/targets/gemini-cli.js.map +2 -2
- package/dist/bin/installer/targets/generic.js +2 -14
- package/dist/bin/installer/targets/generic.js.map +2 -2
- package/dist/bin/installer/targets/goose.js +3 -3
- package/dist/bin/installer/targets/goose.js.map +2 -2
- package/dist/bin/installer/targets/index.js +250 -0
- package/dist/bin/installer/targets/index.js.map +2 -2
- package/dist/bin/installer/targets/junie.js +2 -2
- package/dist/bin/installer/targets/junie.js.map +2 -2
- package/dist/bin/installer/targets/kilo.js +2 -2
- package/dist/bin/installer/targets/kilo.js.map +2 -2
- package/dist/bin/installer/targets/kiro.js +3 -3
- package/dist/bin/installer/targets/kiro.js.map +2 -2
- package/dist/bin/installer/targets/opencode.js +2 -2
- package/dist/bin/installer/targets/opencode.js.map +2 -2
- package/dist/bin/installer/targets/openhands.js +3 -3
- package/dist/bin/installer/targets/openhands.js.map +2 -2
- package/dist/bin/installer/targets/qoder.js +91 -0
- package/dist/bin/installer/targets/qoder.js.map +7 -0
- package/dist/bin/installer/targets/qwen.js +96 -0
- package/dist/bin/installer/targets/qwen.js.map +7 -0
- package/dist/bin/installer/targets/replit.js +3 -3
- package/dist/bin/installer/targets/replit.js.map +2 -2
- package/dist/bin/installer/targets/roo.js +2 -2
- package/dist/bin/installer/targets/roo.js.map +2 -2
- package/dist/bin/installer/targets/tabnine.js +3 -3
- package/dist/bin/installer/targets/tabnine.js.map +2 -2
- package/dist/bin/installer/targets/trae.js +5 -5
- package/dist/bin/installer/targets/trae.js.map +2 -2
- package/dist/bin/installer/targets/warp.js +2 -2
- package/dist/bin/installer/targets/warp.js.map +2 -2
- package/dist/bin/installer/targets/windsurf.js +35 -6
- package/dist/bin/installer/targets/windsurf.js.map +2 -2
- package/dist/bin/kirograph.js +72 -6
- package/dist/bin/kirograph.js.map +3 -3
- package/dist/compression/tracker.js +80 -0
- package/dist/compression/tracker.js.map +2 -2
- package/dist/config.js +90 -3
- package/dist/config.js.map +2 -2
- package/dist/core/pipeline.js +36 -2
- package/dist/core/pipeline.js.map +2 -2
- package/dist/db/database.js +56 -5
- package/dist/db/database.js.map +2 -2
- package/dist/db/memory-schema.sql +5 -1
- package/dist/db/schema.sql +3 -1
- package/dist/db/security-schema.sql +72 -0
- package/dist/extraction/extractor.js +81 -0
- package/dist/extraction/extractor.js.map +2 -2
- package/dist/extraction/grammars.js +32 -8
- package/dist/extraction/grammars.js.map +2 -2
- package/dist/extraction/languages.js +43 -1
- package/dist/extraction/languages.js.map +2 -2
- package/dist/extraction/notebook.js +300 -0
- package/dist/extraction/notebook.js.map +7 -0
- package/dist/extraction/wasm/tree-sitter-astro.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-gdscript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-julia.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-perl.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-powershell.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-r.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-sql.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-verilog.wasm +0 -0
- package/dist/frameworks/flutter.js +130 -0
- package/dist/frameworks/flutter.js.map +7 -0
- package/dist/frameworks/index.js +7 -1
- package/dist/frameworks/index.js.map +3 -3
- package/dist/graph/communities.js +317 -0
- package/dist/graph/communities.js.map +7 -0
- package/dist/graph/flows.js +138 -0
- package/dist/graph/flows.js.map +7 -0
- package/dist/graph/refactor.js +155 -0
- package/dist/graph/refactor.js.map +7 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +2 -2
- package/dist/mcp/cache.js +172 -0
- package/dist/mcp/cache.js.map +7 -0
- package/dist/mcp/read-modes.js +295 -0
- package/dist/mcp/read-modes.js.map +7 -0
- package/dist/mcp/server.js +2 -1
- package/dist/mcp/server.js.map +2 -2
- package/dist/mcp/tool-names.js +21 -1
- package/dist/mcp/tool-names.js.map +2 -2
- package/dist/mcp/tools.js +1160 -1
- package/dist/mcp/tools.js.map +3 -3
- package/dist/memory/database.js +32 -2
- package/dist/memory/database.js.map +2 -2
- package/dist/memory/types.js.map +1 -1
- package/dist/resolution/bridges/android-rn.js +298 -0
- package/dist/resolution/bridges/android-rn.js.map +7 -0
- package/dist/resolution/bridges/expo-modules.js +155 -0
- package/dist/resolution/bridges/expo-modules.js.map +7 -0
- package/dist/resolution/bridges/flutter-channel.js +385 -0
- package/dist/resolution/bridges/flutter-channel.js.map +7 -0
- package/dist/resolution/bridges/index.js +80 -0
- package/dist/resolution/bridges/index.js.map +7 -0
- package/dist/resolution/bridges/native-events.js +188 -0
- package/dist/resolution/bridges/native-events.js.map +7 -0
- package/dist/resolution/bridges/native-views.js +244 -0
- package/dist/resolution/bridges/native-views.js.map +7 -0
- package/dist/resolution/bridges/react-native.js +161 -0
- package/dist/resolution/bridges/react-native.js.map +7 -0
- package/dist/resolution/bridges/swift-objc.js +227 -0
- package/dist/resolution/bridges/swift-objc.js.map +7 -0
- package/dist/resolution/bridges/turbomodules.js +216 -0
- package/dist/resolution/bridges/turbomodules.js.map +7 -0
- package/dist/resolution/index.js +61 -2
- package/dist/resolution/index.js.map +2 -2
- package/dist/security/attack-surface.js +164 -0
- package/dist/security/attack-surface.js.map +7 -0
- package/dist/security/context-warnings.js +123 -0
- package/dist/security/context-warnings.js.map +7 -0
- package/dist/security/context-warnings.test.js +300 -0
- package/dist/security/context-warnings.test.js.map +7 -0
- package/dist/security/data-flows.js +228 -0
- package/dist/security/data-flows.js.map +7 -0
- package/dist/security/dep-confusion.js +255 -0
- package/dist/security/dep-confusion.js.map +7 -0
- package/dist/security/errors.js +51 -0
- package/dist/security/errors.js.map +7 -0
- package/dist/security/export/fix-suggestions.js +58 -0
- package/dist/security/export/fix-suggestions.js.map +7 -0
- package/dist/security/export/fix-suggestions.test.js +74 -0
- package/dist/security/export/fix-suggestions.test.js.map +7 -0
- package/dist/security/export/sbom.js +230 -0
- package/dist/security/export/sbom.js.map +7 -0
- package/dist/security/export/sbom.test.js +288 -0
- package/dist/security/export/sbom.test.js.map +7 -0
- package/dist/security/export/serialization.js +142 -0
- package/dist/security/export/serialization.js.map +7 -0
- package/dist/security/export/serialization.test.js +310 -0
- package/dist/security/export/serialization.test.js.map +7 -0
- package/dist/security/export/vex.js +220 -0
- package/dist/security/export/vex.js.map +7 -0
- package/dist/security/export/vex.test.js +278 -0
- package/dist/security/export/vex.test.js.map +7 -0
- package/dist/security/index.js +67 -0
- package/dist/security/index.js.map +7 -0
- package/dist/security/integrator-transitives.test.js +359 -0
- package/dist/security/integrator-transitives.test.js.map +7 -0
- package/dist/security/integrator.js +491 -0
- package/dist/security/integrator.js.map +7 -0
- package/dist/security/integrator.test.js +237 -0
- package/dist/security/integrator.test.js.map +7 -0
- package/dist/security/license.js +70 -0
- package/dist/security/license.js.map +7 -0
- package/dist/security/manifest/adapter.js +243 -0
- package/dist/security/manifest/adapter.js.map +7 -0
- package/dist/security/manifest/adapter.test.js +272 -0
- package/dist/security/manifest/adapter.test.js.map +7 -0
- package/dist/security/manifest/parser.js +376 -0
- package/dist/security/manifest/parser.js.map +7 -0
- package/dist/security/manifest/parser.test.js +110 -0
- package/dist/security/manifest/parser.test.js.map +7 -0
- package/dist/security/manifest/plugins/cargo.js +233 -0
- package/dist/security/manifest/plugins/cargo.js.map +7 -0
- package/dist/security/manifest/plugins/cargo.test.js +304 -0
- package/dist/security/manifest/plugins/cargo.test.js.map +7 -0
- package/dist/security/manifest/plugins/composer.js +152 -0
- package/dist/security/manifest/plugins/composer.js.map +7 -0
- package/dist/security/manifest/plugins/go.js +146 -0
- package/dist/security/manifest/plugins/go.js.map +7 -0
- package/dist/security/manifest/plugins/go.test.js +341 -0
- package/dist/security/manifest/plugins/go.test.js.map +7 -0
- package/dist/security/manifest/plugins/gradle.js +159 -0
- package/dist/security/manifest/plugins/gradle.js.map +7 -0
- package/dist/security/manifest/plugins/hex.js +159 -0
- package/dist/security/manifest/plugins/hex.js.map +7 -0
- package/dist/security/manifest/plugins/maven.js +128 -0
- package/dist/security/manifest/plugins/maven.js.map +7 -0
- package/dist/security/manifest/plugins/maven.test.js +389 -0
- package/dist/security/manifest/plugins/maven.test.js.map +7 -0
- package/dist/security/manifest/plugins/npm.js +284 -0
- package/dist/security/manifest/plugins/npm.js.map +7 -0
- package/dist/security/manifest/plugins/npm.test.js +298 -0
- package/dist/security/manifest/plugins/npm.test.js.map +7 -0
- package/dist/security/manifest/plugins/nuget.js +186 -0
- package/dist/security/manifest/plugins/nuget.js.map +7 -0
- package/dist/security/manifest/plugins/pip.js +123 -0
- package/dist/security/manifest/plugins/pip.js.map +7 -0
- package/dist/security/manifest/plugins/pip.test.js +208 -0
- package/dist/security/manifest/plugins/pip.test.js.map +7 -0
- package/dist/security/manifest/plugins/pubspec.js +212 -0
- package/dist/security/manifest/plugins/pubspec.js.map +7 -0
- package/dist/security/manifest/plugins/pyproject.js +261 -0
- package/dist/security/manifest/plugins/pyproject.js.map +7 -0
- package/dist/security/manifest/plugins/rubygems.js +185 -0
- package/dist/security/manifest/plugins/rubygems.js.map +7 -0
- package/dist/security/manifest/plugins/swift.js +200 -0
- package/dist/security/manifest/plugins/swift.js.map +7 -0
- package/dist/security/owasp.js +104 -0
- package/dist/security/owasp.js.map +7 -0
- package/dist/security/pipeline.js +125 -0
- package/dist/security/pipeline.js.map +7 -0
- package/dist/security/reachability.js +276 -0
- package/dist/security/reachability.js.map +7 -0
- package/dist/security/reachability.test.js +394 -0
- package/dist/security/reachability.test.js.map +7 -0
- package/dist/security/remediation.js +126 -0
- package/dist/security/remediation.js.map +7 -0
- package/dist/security/secrets.js +176 -0
- package/dist/security/secrets.js.map +7 -0
- package/dist/security/staleness.js +231 -0
- package/dist/security/staleness.js.map +7 -0
- package/dist/security/supply-chain.js +301 -0
- package/dist/security/supply-chain.js.map +7 -0
- package/dist/security/suppressions.js +106 -0
- package/dist/security/suppressions.js.map +7 -0
- package/dist/security/types.js +17 -0
- package/dist/security/types.js.map +7 -0
- package/dist/security/vuln/client.js +355 -0
- package/dist/security/vuln/client.js.map +7 -0
- package/dist/security/vuln/client.test.js +288 -0
- package/dist/security/vuln/client.test.js.map +7 -0
- package/dist/security/vuln/epss-client.js +95 -0
- package/dist/security/vuln/epss-client.js.map +7 -0
- package/dist/security/vuln/index.js +29 -0
- package/dist/security/vuln/index.js.map +7 -0
- package/dist/security/vuln/osv-adapter.js +321 -0
- package/dist/security/vuln/osv-adapter.js.map +7 -0
- package/dist/security/vuln/osv-adapter.test.js +391 -0
- package/dist/security/vuln/osv-adapter.test.js.map +7 -0
- package/dist/security/vuln/types.js +17 -0
- package/dist/security/vuln/types.js.map +7 -0
- package/dist/types.js.map +2 -2
- package/package.json +2 -1
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
var import_vitest = require("vitest");
|
|
25
|
+
var import_context_warnings = require("./context-warnings");
|
|
26
|
+
var import_database = require("../db/database");
|
|
27
|
+
var fs = __toESM(require("fs"));
|
|
28
|
+
var path = __toESM(require("path"));
|
|
29
|
+
var os = __toESM(require("os"));
|
|
30
|
+
function createTempProject() {
|
|
31
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ctx-warn-test-"));
|
|
32
|
+
fs.writeFileSync(
|
|
33
|
+
path.join(dir, "package.json"),
|
|
34
|
+
JSON.stringify({ name: "test-project", version: "1.0.0" })
|
|
35
|
+
);
|
|
36
|
+
return dir;
|
|
37
|
+
}
|
|
38
|
+
function setupDb(projectRoot) {
|
|
39
|
+
const db = new import_database.GraphDatabase(projectRoot);
|
|
40
|
+
db.applySecuritySchema();
|
|
41
|
+
return db;
|
|
42
|
+
}
|
|
43
|
+
function insertEntryPoint(db, nodeId, name) {
|
|
44
|
+
db.upsertNode({
|
|
45
|
+
id: nodeId,
|
|
46
|
+
kind: "function",
|
|
47
|
+
name,
|
|
48
|
+
qualifiedName: name,
|
|
49
|
+
filePath: "src/index.ts",
|
|
50
|
+
language: "typescript",
|
|
51
|
+
startLine: 1,
|
|
52
|
+
endLine: 10,
|
|
53
|
+
startColumn: 0,
|
|
54
|
+
endColumn: 0,
|
|
55
|
+
updatedAt: Date.now()
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function insertVulnerability(db, opts) {
|
|
59
|
+
const rawDb = db.getRawDb();
|
|
60
|
+
db.upsertNode({
|
|
61
|
+
id: opts.depNodeId,
|
|
62
|
+
kind: "dependency",
|
|
63
|
+
name: opts.packageName,
|
|
64
|
+
qualifiedName: `${opts.ecosystem}/${opts.packageName}`,
|
|
65
|
+
filePath: "package.json",
|
|
66
|
+
language: "unknown",
|
|
67
|
+
startLine: 0,
|
|
68
|
+
endLine: 0,
|
|
69
|
+
startColumn: 0,
|
|
70
|
+
endColumn: 0,
|
|
71
|
+
updatedAt: Date.now()
|
|
72
|
+
});
|
|
73
|
+
rawDb.run(
|
|
74
|
+
`INSERT OR REPLACE INTO sec_dependencies
|
|
75
|
+
(node_id, ecosystem, package_name, declared_constraint, resolved_version, scope, source_manifests)
|
|
76
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
77
|
+
[opts.depNodeId, opts.ecosystem, opts.packageName, "^1.0.0", "1.0.0", "production", '["package.json"]']
|
|
78
|
+
);
|
|
79
|
+
db.upsertNode({
|
|
80
|
+
id: opts.vulnNodeId,
|
|
81
|
+
kind: "vulnerability",
|
|
82
|
+
name: opts.cveId,
|
|
83
|
+
qualifiedName: opts.cveId,
|
|
84
|
+
filePath: "package.json",
|
|
85
|
+
language: "unknown",
|
|
86
|
+
startLine: 0,
|
|
87
|
+
endLine: 0,
|
|
88
|
+
startColumn: 0,
|
|
89
|
+
endColumn: 0,
|
|
90
|
+
updatedAt: Date.now()
|
|
91
|
+
});
|
|
92
|
+
rawDb.run(
|
|
93
|
+
`INSERT OR REPLACE INTO sec_vulnerabilities
|
|
94
|
+
(node_id, cve_id, severity_score, affected_ranges, fixed_version, summary, source_database)
|
|
95
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
96
|
+
[opts.vulnNodeId, opts.cveId, opts.severityScore, "[]", opts.fixedVersion ?? null, "Test vulnerability", "OSV"]
|
|
97
|
+
);
|
|
98
|
+
db.insertEdge({
|
|
99
|
+
source: opts.depNodeId,
|
|
100
|
+
target: opts.vulnNodeId,
|
|
101
|
+
kind: "has_vulnerability"
|
|
102
|
+
});
|
|
103
|
+
rawDb.run(
|
|
104
|
+
`INSERT OR REPLACE INTO sec_reachability
|
|
105
|
+
(vulnerability_node_id, verdict, paths, reaching_entry_point_count, analyzed_at)
|
|
106
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
107
|
+
[opts.vulnNodeId, opts.verdict, JSON.stringify(opts.paths), opts.paths.length, Date.now()]
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
(0, import_vitest.describe)("getSecurityWarningsForNodes", () => {
|
|
111
|
+
let projectRoot;
|
|
112
|
+
let db;
|
|
113
|
+
(0, import_vitest.beforeEach)(() => {
|
|
114
|
+
projectRoot = createTempProject();
|
|
115
|
+
db = setupDb(projectRoot);
|
|
116
|
+
});
|
|
117
|
+
(0, import_vitest.afterEach)(() => {
|
|
118
|
+
db.close();
|
|
119
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
120
|
+
});
|
|
121
|
+
(0, import_vitest.it)("returns empty array when no node IDs provided", () => {
|
|
122
|
+
const result = (0, import_context_warnings.getSecurityWarningsForNodes)(db.getRawDb(), []);
|
|
123
|
+
(0, import_vitest.expect)(result).toEqual([]);
|
|
124
|
+
});
|
|
125
|
+
(0, import_vitest.it)("returns empty array when no vulnerabilities exist", () => {
|
|
126
|
+
insertEntryPoint(db, "node-1", "handleRequest");
|
|
127
|
+
const result = (0, import_context_warnings.getSecurityWarningsForNodes)(db.getRawDb(), ["node-1"]);
|
|
128
|
+
(0, import_vitest.expect)(result).toEqual([]);
|
|
129
|
+
});
|
|
130
|
+
(0, import_vitest.it)("returns warnings for vulnerabilities reachable from context nodes", () => {
|
|
131
|
+
insertEntryPoint(db, "entry-1", "handleRequest");
|
|
132
|
+
insertVulnerability(db, {
|
|
133
|
+
vulnNodeId: "vuln-1",
|
|
134
|
+
depNodeId: "dep-1",
|
|
135
|
+
cveId: "CVE-2023-12345",
|
|
136
|
+
severityScore: 9.8,
|
|
137
|
+
fixedVersion: "1.2.0",
|
|
138
|
+
packageName: "express",
|
|
139
|
+
ecosystem: "npm",
|
|
140
|
+
verdict: "affected",
|
|
141
|
+
paths: [{ entryPoint: "entry-1", path: ["entry-1", "mid-1", "dep-1"] }]
|
|
142
|
+
});
|
|
143
|
+
const result = (0, import_context_warnings.getSecurityWarningsForNodes)(db.getRawDb(), ["entry-1"]);
|
|
144
|
+
(0, import_vitest.expect)(result).toHaveLength(1);
|
|
145
|
+
(0, import_vitest.expect)(result[0].cveId).toBe("CVE-2023-12345");
|
|
146
|
+
(0, import_vitest.expect)(result[0].severityScore).toBe(9.8);
|
|
147
|
+
(0, import_vitest.expect)(result[0].packageName).toBe("express");
|
|
148
|
+
(0, import_vitest.expect)(result[0].ecosystem).toBe("npm");
|
|
149
|
+
(0, import_vitest.expect)(result[0].fixedVersion).toBe("1.2.0");
|
|
150
|
+
(0, import_vitest.expect)(result[0].entryPoints).toContain("entry-1");
|
|
151
|
+
});
|
|
152
|
+
(0, import_vitest.it)("returns warnings when a context node is on the path (not just entry point)", () => {
|
|
153
|
+
insertEntryPoint(db, "entry-1", "handleRequest");
|
|
154
|
+
insertEntryPoint(db, "mid-1", "processData");
|
|
155
|
+
insertVulnerability(db, {
|
|
156
|
+
vulnNodeId: "vuln-1",
|
|
157
|
+
depNodeId: "dep-1",
|
|
158
|
+
cveId: "CVE-2023-99999",
|
|
159
|
+
severityScore: 7.5,
|
|
160
|
+
packageName: "lodash",
|
|
161
|
+
ecosystem: "npm",
|
|
162
|
+
verdict: "affected",
|
|
163
|
+
paths: [{ entryPoint: "entry-1", path: ["entry-1", "mid-1", "dep-1"] }]
|
|
164
|
+
});
|
|
165
|
+
const result = (0, import_context_warnings.getSecurityWarningsForNodes)(db.getRawDb(), ["mid-1"]);
|
|
166
|
+
(0, import_vitest.expect)(result).toHaveLength(1);
|
|
167
|
+
(0, import_vitest.expect)(result[0].cveId).toBe("CVE-2023-99999");
|
|
168
|
+
});
|
|
169
|
+
(0, import_vitest.it)("does not return warnings for not_affected vulnerabilities", () => {
|
|
170
|
+
insertEntryPoint(db, "entry-1", "handleRequest");
|
|
171
|
+
insertVulnerability(db, {
|
|
172
|
+
vulnNodeId: "vuln-1",
|
|
173
|
+
depNodeId: "dep-1",
|
|
174
|
+
cveId: "CVE-2023-00001",
|
|
175
|
+
severityScore: 5,
|
|
176
|
+
packageName: "some-pkg",
|
|
177
|
+
ecosystem: "npm",
|
|
178
|
+
verdict: "not_affected",
|
|
179
|
+
paths: []
|
|
180
|
+
});
|
|
181
|
+
const result = (0, import_context_warnings.getSecurityWarningsForNodes)(db.getRawDb(), ["entry-1"]);
|
|
182
|
+
(0, import_vitest.expect)(result).toEqual([]);
|
|
183
|
+
});
|
|
184
|
+
(0, import_vitest.it)("does not return warnings when context nodes are not on any path", () => {
|
|
185
|
+
insertEntryPoint(db, "entry-1", "handleRequest");
|
|
186
|
+
insertEntryPoint(db, "unrelated", "otherFunction");
|
|
187
|
+
insertVulnerability(db, {
|
|
188
|
+
vulnNodeId: "vuln-1",
|
|
189
|
+
depNodeId: "dep-1",
|
|
190
|
+
cveId: "CVE-2023-11111",
|
|
191
|
+
severityScore: 8,
|
|
192
|
+
packageName: "express",
|
|
193
|
+
ecosystem: "npm",
|
|
194
|
+
verdict: "affected",
|
|
195
|
+
paths: [{ entryPoint: "entry-1", path: ["entry-1", "dep-1"] }]
|
|
196
|
+
});
|
|
197
|
+
const result = (0, import_context_warnings.getSecurityWarningsForNodes)(db.getRawDb(), ["unrelated"]);
|
|
198
|
+
(0, import_vitest.expect)(result).toEqual([]);
|
|
199
|
+
});
|
|
200
|
+
(0, import_vitest.it)("sorts warnings by severity (highest first)", () => {
|
|
201
|
+
insertEntryPoint(db, "entry-1", "handleRequest");
|
|
202
|
+
insertVulnerability(db, {
|
|
203
|
+
vulnNodeId: "vuln-1",
|
|
204
|
+
depNodeId: "dep-1",
|
|
205
|
+
cveId: "CVE-2023-LOW",
|
|
206
|
+
severityScore: 3,
|
|
207
|
+
packageName: "low-pkg",
|
|
208
|
+
ecosystem: "npm",
|
|
209
|
+
verdict: "affected",
|
|
210
|
+
paths: [{ entryPoint: "entry-1", path: ["entry-1", "dep-1"] }]
|
|
211
|
+
});
|
|
212
|
+
insertVulnerability(db, {
|
|
213
|
+
vulnNodeId: "vuln-2",
|
|
214
|
+
depNodeId: "dep-2",
|
|
215
|
+
cveId: "CVE-2023-HIGH",
|
|
216
|
+
severityScore: 9.5,
|
|
217
|
+
packageName: "high-pkg",
|
|
218
|
+
ecosystem: "npm",
|
|
219
|
+
verdict: "affected",
|
|
220
|
+
paths: [{ entryPoint: "entry-1", path: ["entry-1", "dep-2"] }]
|
|
221
|
+
});
|
|
222
|
+
const result = (0, import_context_warnings.getSecurityWarningsForNodes)(db.getRawDb(), ["entry-1"]);
|
|
223
|
+
(0, import_vitest.expect)(result).toHaveLength(2);
|
|
224
|
+
(0, import_vitest.expect)(result[0].cveId).toBe("CVE-2023-HIGH");
|
|
225
|
+
(0, import_vitest.expect)(result[1].cveId).toBe("CVE-2023-LOW");
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
(0, import_vitest.describe)("formatSecurityWarnings", () => {
|
|
229
|
+
(0, import_vitest.it)("returns empty string when no warnings", () => {
|
|
230
|
+
(0, import_vitest.expect)((0, import_context_warnings.formatSecurityWarnings)([], /* @__PURE__ */ new Map())).toBe("");
|
|
231
|
+
});
|
|
232
|
+
(0, import_vitest.it)("formats a single warning with severity and entry point", () => {
|
|
233
|
+
const warnings = [
|
|
234
|
+
{
|
|
235
|
+
cveId: "CVE-2023-12345",
|
|
236
|
+
severityScore: 9.8,
|
|
237
|
+
packageName: "express",
|
|
238
|
+
ecosystem: "npm",
|
|
239
|
+
fixedVersion: "4.18.3",
|
|
240
|
+
entryPoints: ["entry-1"]
|
|
241
|
+
}
|
|
242
|
+
];
|
|
243
|
+
const nodeNames = /* @__PURE__ */ new Map([["entry-1", "handleRequest"]]);
|
|
244
|
+
const result = (0, import_context_warnings.formatSecurityWarnings)(warnings, nodeNames);
|
|
245
|
+
(0, import_vitest.expect)(result).toContain("## \u26A0 Security");
|
|
246
|
+
(0, import_vitest.expect)(result).toContain("**CVE-2023-12345**");
|
|
247
|
+
(0, import_vitest.expect)(result).toContain("CVSS 9.8");
|
|
248
|
+
(0, import_vitest.expect)(result).toContain("express (npm)");
|
|
249
|
+
(0, import_vitest.expect)(result).toContain("handleRequest");
|
|
250
|
+
(0, import_vitest.expect)(result).toContain("\u{1F4A1} Fix: npm install express@4.18.3");
|
|
251
|
+
});
|
|
252
|
+
(0, import_vitest.it)('shows max 3 vulnerabilities and a "more" note', () => {
|
|
253
|
+
const warnings = Array.from({ length: 5 }, (_, i) => ({
|
|
254
|
+
cveId: `CVE-2023-${i}`,
|
|
255
|
+
severityScore: 9 - i,
|
|
256
|
+
packageName: `pkg-${i}`,
|
|
257
|
+
ecosystem: "npm",
|
|
258
|
+
fixedVersion: null,
|
|
259
|
+
entryPoints: ["entry-1"]
|
|
260
|
+
}));
|
|
261
|
+
const nodeNames = /* @__PURE__ */ new Map([["entry-1", "main"]]);
|
|
262
|
+
const result = (0, import_context_warnings.formatSecurityWarnings)(warnings, nodeNames);
|
|
263
|
+
(0, import_vitest.expect)(result).toContain("CVE-2023-0");
|
|
264
|
+
(0, import_vitest.expect)(result).toContain("CVE-2023-1");
|
|
265
|
+
(0, import_vitest.expect)(result).toContain("CVE-2023-2");
|
|
266
|
+
(0, import_vitest.expect)(result).not.toContain("CVE-2023-3");
|
|
267
|
+
(0, import_vitest.expect)(result).not.toContain("CVE-2023-4");
|
|
268
|
+
(0, import_vitest.expect)(result).toContain("2 more \u2014 use kirograph_vulns for full list");
|
|
269
|
+
});
|
|
270
|
+
(0, import_vitest.it)("does not show fix suggestion when fixedVersion is null", () => {
|
|
271
|
+
const warnings = [
|
|
272
|
+
{
|
|
273
|
+
cveId: "CVE-2023-99999",
|
|
274
|
+
severityScore: 7,
|
|
275
|
+
packageName: "lodash",
|
|
276
|
+
ecosystem: "npm",
|
|
277
|
+
fixedVersion: null,
|
|
278
|
+
entryPoints: ["entry-1"]
|
|
279
|
+
}
|
|
280
|
+
];
|
|
281
|
+
const nodeNames = /* @__PURE__ */ new Map([["entry-1", "main"]]);
|
|
282
|
+
const result = (0, import_context_warnings.formatSecurityWarnings)(warnings, nodeNames);
|
|
283
|
+
(0, import_vitest.expect)(result).not.toContain("\u{1F4A1} Fix");
|
|
284
|
+
});
|
|
285
|
+
(0, import_vitest.it)("uses node ID when name is not in the map", () => {
|
|
286
|
+
const warnings = [
|
|
287
|
+
{
|
|
288
|
+
cveId: "CVE-2023-00001",
|
|
289
|
+
severityScore: 5,
|
|
290
|
+
packageName: "some-pkg",
|
|
291
|
+
ecosystem: "npm",
|
|
292
|
+
fixedVersion: null,
|
|
293
|
+
entryPoints: ["unknown-id"]
|
|
294
|
+
}
|
|
295
|
+
];
|
|
296
|
+
const result = (0, import_context_warnings.formatSecurityWarnings)(warnings, /* @__PURE__ */ new Map());
|
|
297
|
+
(0, import_vitest.expect)(result).toContain("unknown-id");
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
//# sourceMappingURL=context-warnings.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/security/context-warnings.test.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Unit tests for security context warnings\n */\nimport { describe, it, expect, beforeEach, afterEach } from 'vitest';\nimport { getSecurityWarningsForNodes, formatSecurityWarnings } from './context-warnings';\nimport type { SecurityWarning } from './context-warnings';\nimport { GraphDatabase } from '../db/database';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as os from 'os';\n\n// \u2500\u2500 Test Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 createTempProject(): string {\n const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ctx-warn-test-'));\n fs.writeFileSync(\n path.join(dir, 'package.json'),\n JSON.stringify({ name: 'test-project', version: '1.0.0' }),\n );\n return dir;\n}\n\nfunction setupDb(projectRoot: string): GraphDatabase {\n const db = new GraphDatabase(projectRoot);\n db.applySecuritySchema();\n return db;\n}\n\nfunction insertEntryPoint(db: GraphDatabase, nodeId: string, name: string): void {\n db.upsertNode({\n id: nodeId,\n kind: 'function',\n name,\n qualifiedName: name,\n filePath: 'src/index.ts',\n language: 'typescript',\n startLine: 1,\n endLine: 10,\n startColumn: 0,\n endColumn: 0,\n updatedAt: Date.now(),\n });\n}\n\nfunction insertVulnerability(\n db: GraphDatabase,\n opts: {\n vulnNodeId: string;\n depNodeId: string;\n cveId: string;\n severityScore: number;\n fixedVersion?: string;\n packageName: string;\n ecosystem: string;\n verdict: string;\n paths: Array<{ entryPoint: string; path: string[] }>;\n },\n): void {\n const rawDb = db.getRawDb();\n\n // Insert dependency node\n db.upsertNode({\n id: opts.depNodeId,\n kind: 'dependency',\n name: opts.packageName,\n qualifiedName: `${opts.ecosystem}/${opts.packageName}`,\n filePath: 'package.json',\n language: 'unknown',\n startLine: 0,\n endLine: 0,\n startColumn: 0,\n endColumn: 0,\n updatedAt: Date.now(),\n });\n\n rawDb.run(\n `INSERT OR REPLACE INTO sec_dependencies\n (node_id, ecosystem, package_name, declared_constraint, resolved_version, scope, source_manifests)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [opts.depNodeId, opts.ecosystem, opts.packageName, '^1.0.0', '1.0.0', 'production', '[\"package.json\"]'],\n );\n\n // Insert vulnerability node\n db.upsertNode({\n id: opts.vulnNodeId,\n kind: 'vulnerability',\n name: opts.cveId,\n qualifiedName: opts.cveId,\n filePath: 'package.json',\n language: 'unknown',\n startLine: 0,\n endLine: 0,\n startColumn: 0,\n endColumn: 0,\n updatedAt: Date.now(),\n });\n\n rawDb.run(\n `INSERT OR REPLACE INTO sec_vulnerabilities\n (node_id, cve_id, severity_score, affected_ranges, fixed_version, summary, source_database)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [opts.vulnNodeId, opts.cveId, opts.severityScore, '[]', opts.fixedVersion ?? null, 'Test vulnerability', 'OSV'],\n );\n\n // Insert has_vulnerability edge (from dependency to vulnerability)\n db.insertEdge({\n source: opts.depNodeId,\n target: opts.vulnNodeId,\n kind: 'has_vulnerability',\n });\n\n // Insert reachability record\n rawDb.run(\n `INSERT OR REPLACE INTO sec_reachability\n (vulnerability_node_id, verdict, paths, reaching_entry_point_count, analyzed_at)\n VALUES (?, ?, ?, ?, ?)`,\n [opts.vulnNodeId, opts.verdict, JSON.stringify(opts.paths), opts.paths.length, Date.now()],\n );\n}\n\n// \u2500\u2500 Tests \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\ndescribe('getSecurityWarningsForNodes', () => {\n let projectRoot: string;\n let db: GraphDatabase;\n\n beforeEach(() => {\n projectRoot = createTempProject();\n db = setupDb(projectRoot);\n });\n\n afterEach(() => {\n db.close();\n fs.rmSync(projectRoot, { recursive: true, force: true });\n });\n\n it('returns empty array when no node IDs provided', () => {\n const result = getSecurityWarningsForNodes(db.getRawDb(), []);\n expect(result).toEqual([]);\n });\n\n it('returns empty array when no vulnerabilities exist', () => {\n insertEntryPoint(db, 'node-1', 'handleRequest');\n const result = getSecurityWarningsForNodes(db.getRawDb(), ['node-1']);\n expect(result).toEqual([]);\n });\n\n it('returns warnings for vulnerabilities reachable from context nodes', () => {\n insertEntryPoint(db, 'entry-1', 'handleRequest');\n insertVulnerability(db, {\n vulnNodeId: 'vuln-1',\n depNodeId: 'dep-1',\n cveId: 'CVE-2023-12345',\n severityScore: 9.8,\n fixedVersion: '1.2.0',\n packageName: 'express',\n ecosystem: 'npm',\n verdict: 'affected',\n paths: [{ entryPoint: 'entry-1', path: ['entry-1', 'mid-1', 'dep-1'] }],\n });\n\n const result = getSecurityWarningsForNodes(db.getRawDb(), ['entry-1']);\n expect(result).toHaveLength(1);\n expect(result[0].cveId).toBe('CVE-2023-12345');\n expect(result[0].severityScore).toBe(9.8);\n expect(result[0].packageName).toBe('express');\n expect(result[0].ecosystem).toBe('npm');\n expect(result[0].fixedVersion).toBe('1.2.0');\n expect(result[0].entryPoints).toContain('entry-1');\n });\n\n it('returns warnings when a context node is on the path (not just entry point)', () => {\n insertEntryPoint(db, 'entry-1', 'handleRequest');\n insertEntryPoint(db, 'mid-1', 'processData');\n insertVulnerability(db, {\n vulnNodeId: 'vuln-1',\n depNodeId: 'dep-1',\n cveId: 'CVE-2023-99999',\n severityScore: 7.5,\n packageName: 'lodash',\n ecosystem: 'npm',\n verdict: 'affected',\n paths: [{ entryPoint: 'entry-1', path: ['entry-1', 'mid-1', 'dep-1'] }],\n });\n\n // Query with mid-1 (intermediate node on path)\n const result = getSecurityWarningsForNodes(db.getRawDb(), ['mid-1']);\n expect(result).toHaveLength(1);\n expect(result[0].cveId).toBe('CVE-2023-99999');\n });\n\n it('does not return warnings for not_affected vulnerabilities', () => {\n insertEntryPoint(db, 'entry-1', 'handleRequest');\n insertVulnerability(db, {\n vulnNodeId: 'vuln-1',\n depNodeId: 'dep-1',\n cveId: 'CVE-2023-00001',\n severityScore: 5.0,\n packageName: 'some-pkg',\n ecosystem: 'npm',\n verdict: 'not_affected',\n paths: [],\n });\n\n const result = getSecurityWarningsForNodes(db.getRawDb(), ['entry-1']);\n expect(result).toEqual([]);\n });\n\n it('does not return warnings when context nodes are not on any path', () => {\n insertEntryPoint(db, 'entry-1', 'handleRequest');\n insertEntryPoint(db, 'unrelated', 'otherFunction');\n insertVulnerability(db, {\n vulnNodeId: 'vuln-1',\n depNodeId: 'dep-1',\n cveId: 'CVE-2023-11111',\n severityScore: 8.0,\n packageName: 'express',\n ecosystem: 'npm',\n verdict: 'affected',\n paths: [{ entryPoint: 'entry-1', path: ['entry-1', 'dep-1'] }],\n });\n\n // Query with 'unrelated' which is not on any path\n const result = getSecurityWarningsForNodes(db.getRawDb(), ['unrelated']);\n expect(result).toEqual([]);\n });\n\n it('sorts warnings by severity (highest first)', () => {\n insertEntryPoint(db, 'entry-1', 'handleRequest');\n insertVulnerability(db, {\n vulnNodeId: 'vuln-1',\n depNodeId: 'dep-1',\n cveId: 'CVE-2023-LOW',\n severityScore: 3.0,\n packageName: 'low-pkg',\n ecosystem: 'npm',\n verdict: 'affected',\n paths: [{ entryPoint: 'entry-1', path: ['entry-1', 'dep-1'] }],\n });\n insertVulnerability(db, {\n vulnNodeId: 'vuln-2',\n depNodeId: 'dep-2',\n cveId: 'CVE-2023-HIGH',\n severityScore: 9.5,\n packageName: 'high-pkg',\n ecosystem: 'npm',\n verdict: 'affected',\n paths: [{ entryPoint: 'entry-1', path: ['entry-1', 'dep-2'] }],\n });\n\n const result = getSecurityWarningsForNodes(db.getRawDb(), ['entry-1']);\n expect(result).toHaveLength(2);\n expect(result[0].cveId).toBe('CVE-2023-HIGH');\n expect(result[1].cveId).toBe('CVE-2023-LOW');\n });\n});\n\ndescribe('formatSecurityWarnings', () => {\n it('returns empty string when no warnings', () => {\n expect(formatSecurityWarnings([], new Map())).toBe('');\n });\n\n it('formats a single warning with severity and entry point', () => {\n const warnings: SecurityWarning[] = [\n {\n cveId: 'CVE-2023-12345',\n severityScore: 9.8,\n packageName: 'express',\n ecosystem: 'npm',\n fixedVersion: '4.18.3',\n entryPoints: ['entry-1'],\n },\n ];\n const nodeNames = new Map([['entry-1', 'handleRequest']]);\n\n const result = formatSecurityWarnings(warnings, nodeNames);\n expect(result).toContain('## \u26A0 Security');\n expect(result).toContain('**CVE-2023-12345**');\n expect(result).toContain('CVSS 9.8');\n expect(result).toContain('express (npm)');\n expect(result).toContain('handleRequest');\n expect(result).toContain('\uD83D\uDCA1 Fix: npm install express@4.18.3');\n });\n\n it('shows max 3 vulnerabilities and a \"more\" note', () => {\n const warnings: SecurityWarning[] = Array.from({ length: 5 }, (_, i) => ({\n cveId: `CVE-2023-${i}`,\n severityScore: 9.0 - i,\n packageName: `pkg-${i}`,\n ecosystem: 'npm',\n fixedVersion: null,\n entryPoints: ['entry-1'],\n }));\n const nodeNames = new Map([['entry-1', 'main']]);\n\n const result = formatSecurityWarnings(warnings, nodeNames);\n expect(result).toContain('CVE-2023-0');\n expect(result).toContain('CVE-2023-1');\n expect(result).toContain('CVE-2023-2');\n expect(result).not.toContain('CVE-2023-3');\n expect(result).not.toContain('CVE-2023-4');\n expect(result).toContain('2 more \u2014 use kirograph_vulns for full list');\n });\n\n it('does not show fix suggestion when fixedVersion is null', () => {\n const warnings: SecurityWarning[] = [\n {\n cveId: 'CVE-2023-99999',\n severityScore: 7.0,\n packageName: 'lodash',\n ecosystem: 'npm',\n fixedVersion: null,\n entryPoints: ['entry-1'],\n },\n ];\n const nodeNames = new Map([['entry-1', 'main']]);\n\n const result = formatSecurityWarnings(warnings, nodeNames);\n expect(result).not.toContain('\uD83D\uDCA1 Fix');\n });\n\n it('uses node ID when name is not in the map', () => {\n const warnings: SecurityWarning[] = [\n {\n cveId: 'CVE-2023-00001',\n severityScore: 5.0,\n packageName: 'some-pkg',\n ecosystem: 'npm',\n fixedVersion: null,\n entryPoints: ['unknown-id'],\n },\n ];\n\n const result = formatSecurityWarnings(warnings, new Map());\n expect(result).toContain('unknown-id');\n });\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var data_flows_exports = {};
|
|
20
|
+
__export(data_flows_exports, {
|
|
21
|
+
DataFlowAnalyzer: () => DataFlowAnalyzer
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(data_flows_exports);
|
|
24
|
+
class DataFlowAnalyzer {
|
|
25
|
+
constructor(db) {
|
|
26
|
+
this.db = db;
|
|
27
|
+
}
|
|
28
|
+
async analyze() {
|
|
29
|
+
const rawDb = this.db.getRawDb();
|
|
30
|
+
const findings = [];
|
|
31
|
+
findings.push(...this._detectDangerousEval(rawDb));
|
|
32
|
+
findings.push(...this._detectUnsafeDeserialize(rawDb));
|
|
33
|
+
findings.push(...this._detectHardcodedCrypto(rawDb));
|
|
34
|
+
findings.push(...this._detectSqlInjection(rawDb));
|
|
35
|
+
findings.push(...this._detectPathTraversal(rawDb));
|
|
36
|
+
return findings;
|
|
37
|
+
}
|
|
38
|
+
// ── Detection: dangerous eval / exec ────────────────────────────────────────
|
|
39
|
+
_detectDangerousEval(rawDb) {
|
|
40
|
+
const rows = rawDb.all(`
|
|
41
|
+
SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee
|
|
42
|
+
FROM edges e
|
|
43
|
+
JOIN nodes n ON n.id = e.source
|
|
44
|
+
JOIN nodes n2 ON n2.id = e.target
|
|
45
|
+
WHERE e.kind = 'calls'
|
|
46
|
+
AND n2.name IN ('eval', 'Function', 'execSync', 'exec', 'spawn', 'spawnSync', 'execFileSync', 'execFile')
|
|
47
|
+
AND n.file_path != ''
|
|
48
|
+
`);
|
|
49
|
+
return rows.map((row) => ({
|
|
50
|
+
type: "dangerous-eval",
|
|
51
|
+
severity: "critical",
|
|
52
|
+
owaspCategory: "A03",
|
|
53
|
+
filePath: row.file_path,
|
|
54
|
+
line: row.start_line,
|
|
55
|
+
symbol: row.caller,
|
|
56
|
+
description: `Function '${row.caller}' calls '${row.callee}', which can execute arbitrary code or OS commands.`,
|
|
57
|
+
recommendation: `Avoid '${row.callee}'. Use structured APIs or allowlists for dynamic execution. Validate and sanitize all inputs.`
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
// ── Detection: unsafe deserialization ────────────────────────────────────────
|
|
61
|
+
_detectUnsafeDeserialize(rawDb) {
|
|
62
|
+
const rows = rawDb.all(`
|
|
63
|
+
SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee
|
|
64
|
+
FROM edges e
|
|
65
|
+
JOIN nodes n ON n.id = e.source
|
|
66
|
+
JOIN nodes n2 ON n2.id = e.target
|
|
67
|
+
WHERE e.kind = 'calls'
|
|
68
|
+
AND (
|
|
69
|
+
n2.name IN ('unserialize', 'deserialize', 'fromJson', 'loads')
|
|
70
|
+
OR n2.name LIKE '%pickle%'
|
|
71
|
+
OR n2.name LIKE '%marshal%'
|
|
72
|
+
OR n2.name = 'yaml.load'
|
|
73
|
+
)
|
|
74
|
+
AND n.file_path != ''
|
|
75
|
+
`);
|
|
76
|
+
return rows.map((row) => ({
|
|
77
|
+
type: "unsafe-deserialize",
|
|
78
|
+
severity: "high",
|
|
79
|
+
owaspCategory: "A08",
|
|
80
|
+
filePath: row.file_path,
|
|
81
|
+
line: row.start_line,
|
|
82
|
+
symbol: row.caller,
|
|
83
|
+
description: `Function '${row.caller}' calls '${row.callee}', which may deserialize untrusted data unsafely.`,
|
|
84
|
+
recommendation: `Validate data before deserialization. Use safe alternatives (e.g. yaml.safe_load, JSON schema validation). Never deserialize data from untrusted sources.`
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
// ── Detection: hardcoded weak crypto ─────────────────────────────────────────
|
|
88
|
+
_detectHardcodedCrypto(rawDb) {
|
|
89
|
+
const rows = rawDb.all(`
|
|
90
|
+
SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee
|
|
91
|
+
FROM edges e
|
|
92
|
+
JOIN nodes n ON n.id = e.source
|
|
93
|
+
JOIN nodes n2 ON n2.id = e.target
|
|
94
|
+
WHERE e.kind = 'calls'
|
|
95
|
+
AND (
|
|
96
|
+
n2.name LIKE '%md5%'
|
|
97
|
+
OR n2.name LIKE '%sha1%'
|
|
98
|
+
OR n2.name LIKE '%des%'
|
|
99
|
+
OR n2.name LIKE '%rc4%'
|
|
100
|
+
OR n2.name LIKE '%blowfish%'
|
|
101
|
+
)
|
|
102
|
+
AND (
|
|
103
|
+
n.name LIKE '%crypto%'
|
|
104
|
+
OR n.name LIKE '%hash%'
|
|
105
|
+
OR n.name LIKE '%cipher%'
|
|
106
|
+
OR n.name LIKE '%encrypt%'
|
|
107
|
+
OR n.name LIKE '%digest%'
|
|
108
|
+
OR n.file_path LIKE '%crypto%'
|
|
109
|
+
OR n.file_path LIKE '%hash%'
|
|
110
|
+
OR n.file_path LIKE '%cipher%'
|
|
111
|
+
OR n.file_path LIKE '%auth%'
|
|
112
|
+
)
|
|
113
|
+
AND n.file_path != ''
|
|
114
|
+
`);
|
|
115
|
+
return rows.map((row) => ({
|
|
116
|
+
type: "hardcoded-crypto",
|
|
117
|
+
severity: "medium",
|
|
118
|
+
owaspCategory: "A02",
|
|
119
|
+
filePath: row.file_path,
|
|
120
|
+
line: row.start_line,
|
|
121
|
+
symbol: row.caller,
|
|
122
|
+
description: `Function '${row.caller}' uses weak or deprecated cryptographic algorithm '${row.callee}'.`,
|
|
123
|
+
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.`
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
// ── Detection: SQL injection ──────────────────────────────────────────────────
|
|
127
|
+
_detectSqlInjection(rawDb) {
|
|
128
|
+
const rows = rawDb.all(`
|
|
129
|
+
SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee
|
|
130
|
+
FROM edges e
|
|
131
|
+
JOIN nodes n ON n.id = e.source
|
|
132
|
+
JOIN nodes n2 ON n2.id = e.target
|
|
133
|
+
WHERE e.kind = 'calls'
|
|
134
|
+
AND n2.name IN ('query', 'execute', 'exec', 'raw', 'runQuery', 'executeQuery', 'runSql', 'executeSql')
|
|
135
|
+
AND (
|
|
136
|
+
n.name LIKE '%handle%'
|
|
137
|
+
OR n.name LIKE '%controller%'
|
|
138
|
+
OR n.name LIKE '%route%'
|
|
139
|
+
OR n.name LIKE '%request%'
|
|
140
|
+
OR n.name LIKE '%req%'
|
|
141
|
+
OR n.name LIKE '%endpoint%'
|
|
142
|
+
OR n.name LIKE '%action%'
|
|
143
|
+
OR n.name LIKE '%handler%'
|
|
144
|
+
)
|
|
145
|
+
AND n.file_path != ''
|
|
146
|
+
`);
|
|
147
|
+
const sanitizeCallers = new Set(
|
|
148
|
+
rawDb.all(`
|
|
149
|
+
SELECT DISTINCT n.id
|
|
150
|
+
FROM edges e
|
|
151
|
+
JOIN nodes n ON n.id = e.source
|
|
152
|
+
JOIN nodes n2 ON n2.id = e.target
|
|
153
|
+
WHERE e.kind = 'calls'
|
|
154
|
+
AND (
|
|
155
|
+
n2.name LIKE '%sanitize%'
|
|
156
|
+
OR n2.name LIKE '%escape%'
|
|
157
|
+
OR n2.name LIKE '%parameteriz%'
|
|
158
|
+
OR n2.name LIKE '%prepared%'
|
|
159
|
+
OR n2.name LIKE '%placeholder%'
|
|
160
|
+
)
|
|
161
|
+
`).map((r) => r.id)
|
|
162
|
+
);
|
|
163
|
+
const callerIds = new Map(
|
|
164
|
+
rawDb.all(`
|
|
165
|
+
SELECT id, name FROM nodes WHERE kind IN ('function', 'method', 'arrow_function')
|
|
166
|
+
`).map((r) => [r.name, r.id])
|
|
167
|
+
);
|
|
168
|
+
return rows.filter((row) => {
|
|
169
|
+
const callerId = callerIds.get(row.caller);
|
|
170
|
+
return !callerId || !sanitizeCallers.has(callerId);
|
|
171
|
+
}).map((row) => ({
|
|
172
|
+
type: "sql-injection",
|
|
173
|
+
severity: "critical",
|
|
174
|
+
owaspCategory: "A03",
|
|
175
|
+
filePath: row.file_path,
|
|
176
|
+
line: row.start_line,
|
|
177
|
+
symbol: row.caller,
|
|
178
|
+
description: `Controller/handler '${row.caller}' calls database function '${row.callee}' without apparent input sanitization.`,
|
|
179
|
+
recommendation: `Use parameterized queries or prepared statements. Never concatenate user input into SQL strings. Consider an ORM with query builders.`
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
// ── Detection: path traversal ─────────────────────────────────────────────────
|
|
183
|
+
_detectPathTraversal(rawDb) {
|
|
184
|
+
const rows = rawDb.all(`
|
|
185
|
+
SELECT n.name as caller, n.file_path, n.start_line, n2.name as callee
|
|
186
|
+
FROM edges e
|
|
187
|
+
JOIN nodes n ON n.id = e.source
|
|
188
|
+
JOIN nodes n2 ON n2.id = e.target
|
|
189
|
+
WHERE e.kind = 'calls'
|
|
190
|
+
AND n2.name IN ('readFile', 'readFileSync', 'writeFile', 'writeFileSync', 'createReadStream', 'createWriteStream', 'openSync', 'unlink', 'unlinkSync')
|
|
191
|
+
AND (
|
|
192
|
+
n.name LIKE '%controller%'
|
|
193
|
+
OR n.name LIKE '%route%'
|
|
194
|
+
OR n.name LIKE '%request%'
|
|
195
|
+
OR n.name LIKE '%download%'
|
|
196
|
+
OR n.name LIKE '%upload%'
|
|
197
|
+
OR n.name LIKE '%get%file%'
|
|
198
|
+
OR n.name LIKE '%fetch%file%'
|
|
199
|
+
OR n.name LIKE '%serve%file%'
|
|
200
|
+
OR n.name LIKE '%handle%file%'
|
|
201
|
+
)
|
|
202
|
+
AND n.file_path != ''
|
|
203
|
+
AND n.file_path NOT LIKE '%/mcp/%'
|
|
204
|
+
AND n.file_path NOT LIKE '%/resolution/%'
|
|
205
|
+
AND n.file_path NOT LIKE '%/security/%'
|
|
206
|
+
AND n.file_path NOT LIKE '%/extraction/%'
|
|
207
|
+
AND n.file_path NOT LIKE '%/graph/%'
|
|
208
|
+
AND n.file_path NOT LIKE '%/architecture/%'
|
|
209
|
+
AND n.file_path NOT LIKE '%/frameworks/%'
|
|
210
|
+
AND n.file_path NOT LIKE '%/bridges/%'
|
|
211
|
+
`);
|
|
212
|
+
return rows.map((row) => ({
|
|
213
|
+
type: "path-traversal",
|
|
214
|
+
severity: "high",
|
|
215
|
+
owaspCategory: "A01",
|
|
216
|
+
filePath: row.file_path,
|
|
217
|
+
line: row.start_line,
|
|
218
|
+
symbol: row.caller,
|
|
219
|
+
description: `Handler '${row.caller}' calls file operation '${row.callee}'. Path may be influenced by user input, enabling directory traversal.`,
|
|
220
|
+
recommendation: `Validate and normalize file paths. Use path.resolve() and verify the result is inside an allowed base directory. Reject paths containing '..'.`
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
225
|
+
0 && (module.exports = {
|
|
226
|
+
DataFlowAnalyzer
|
|
227
|
+
});
|
|
228
|
+
//# sourceMappingURL=data-flows.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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 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.getRawDb();\n const findings: DataFlowFinding[] = [];\n\n findings.push(...this._detectDangerousEval(rawDb));\n findings.push(...this._detectUnsafeDeserialize(rawDb));\n findings.push(...this._detectHardcodedCrypto(rawDb));\n findings.push(...this._detectSqlInjection(rawDb));\n findings.push(...this._detectPathTraversal(rawDb));\n\n return findings;\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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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|