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,301 @@
|
|
|
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 supply_chain_exports = {};
|
|
20
|
+
__export(supply_chain_exports, {
|
|
21
|
+
SupplyChainChecker: () => SupplyChainChecker
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(supply_chain_exports);
|
|
24
|
+
var import_errors = require("../errors");
|
|
25
|
+
const TIMEOUT_MS = 1e4;
|
|
26
|
+
async function fetchJson(url, headers) {
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
29
|
+
try {
|
|
30
|
+
const resp = await fetch(url, {
|
|
31
|
+
signal: controller.signal,
|
|
32
|
+
headers: { "Accept": "application/json", ...headers }
|
|
33
|
+
});
|
|
34
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
35
|
+
return await resp.json();
|
|
36
|
+
} finally {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function getGithubRepoFromNpm(name) {
|
|
41
|
+
try {
|
|
42
|
+
const encoded = encodeURIComponent(name);
|
|
43
|
+
const data = await fetchJson(`https://registry.npmjs.org/${encoded}/latest`);
|
|
44
|
+
const repo = data["repository"];
|
|
45
|
+
const url = typeof repo === "string" ? repo : repo?.url ?? null;
|
|
46
|
+
return extractGithubPath(url);
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function getGithubRepoFromCargo(name) {
|
|
52
|
+
try {
|
|
53
|
+
const data = await fetchJson(
|
|
54
|
+
`https://crates.io/api/v1/crates/${encodeURIComponent(name)}`,
|
|
55
|
+
{ "User-Agent": "KiroGraph/1.0" }
|
|
56
|
+
);
|
|
57
|
+
const crate = data["crate"];
|
|
58
|
+
const repo = crate["repository"];
|
|
59
|
+
return extractGithubPath(repo ?? null);
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function getGithubRepoFromPypi(name) {
|
|
65
|
+
try {
|
|
66
|
+
const data = await fetchJson(`https://pypi.org/pypi/${encodeURIComponent(name)}/json`);
|
|
67
|
+
const info = data["info"];
|
|
68
|
+
const projectUrls = info["project_urls"];
|
|
69
|
+
const homePage = info["home_page"];
|
|
70
|
+
const sourceUrl = projectUrls?.["Source"] ?? projectUrls?.["source"] ?? projectUrls?.["Homepage"] ?? homePage ?? null;
|
|
71
|
+
return extractGithubPath(sourceUrl);
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function extractGithubPath(url) {
|
|
77
|
+
if (!url) return null;
|
|
78
|
+
const cleaned = url.replace(/^git\+/, "").replace(/^git:\/\//, "https://").replace(/^ssh:\/\/git@/, "https://").replace(/^git@github\.com:/, "https://github.com/").replace(/\.git$/, "");
|
|
79
|
+
const m = cleaned.match(/github\.com[/:]([^/\s]+\/[^/\s#?]+)/);
|
|
80
|
+
if (!m) return null;
|
|
81
|
+
return m[1];
|
|
82
|
+
}
|
|
83
|
+
async function resolveGithubRepo(ecosystem, name) {
|
|
84
|
+
const eco = ecosystem.toLowerCase();
|
|
85
|
+
switch (eco) {
|
|
86
|
+
case "npm":
|
|
87
|
+
return getGithubRepoFromNpm(name);
|
|
88
|
+
case "cargo":
|
|
89
|
+
return getGithubRepoFromCargo(name);
|
|
90
|
+
case "pypi":
|
|
91
|
+
case "pip":
|
|
92
|
+
return getGithubRepoFromPypi(name);
|
|
93
|
+
default:
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function fetchScorecard(githubRepo) {
|
|
98
|
+
try {
|
|
99
|
+
const data = await fetchJson(`https://api.securityscorecards.dev/projects/github.com/${githubRepo}`);
|
|
100
|
+
const score = data["score"];
|
|
101
|
+
const checksRaw = data["checks"] ?? [];
|
|
102
|
+
const checks = {};
|
|
103
|
+
for (const c of checksRaw) {
|
|
104
|
+
const checkName = c["name"];
|
|
105
|
+
const checkScore = c["score"];
|
|
106
|
+
if (checkName) checks[checkName] = checkScore ?? 0;
|
|
107
|
+
}
|
|
108
|
+
return { score, checks };
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function getNpmMaintenance(name) {
|
|
114
|
+
try {
|
|
115
|
+
const encoded = encodeURIComponent(name);
|
|
116
|
+
const full = await fetchJson(`https://registry.npmjs.org/${encoded}`);
|
|
117
|
+
const time = full["time"] ?? {};
|
|
118
|
+
const maintainers = full["maintainers"] ?? [];
|
|
119
|
+
const maintainerCount = maintainers.length;
|
|
120
|
+
const timeValues = Object.entries(time).filter(([k]) => k !== "created" && k !== "modified").map(([, v]) => new Date(v).getTime()).filter((t) => !isNaN(t));
|
|
121
|
+
const lastPublishMs = timeValues.length > 0 ? Math.max(...timeValues) : null;
|
|
122
|
+
const daysSinceLastCommit = lastPublishMs !== null ? Math.floor((Date.now() - lastPublishMs) / (1e3 * 60 * 60 * 24)) : null;
|
|
123
|
+
const createdStr = time["created"] ?? null;
|
|
124
|
+
const publishedAt = createdStr || null;
|
|
125
|
+
return { daysSinceLastCommit, maintainerCount: maintainerCount || null, publishedAt };
|
|
126
|
+
} catch {
|
|
127
|
+
return { daysSinceLastCommit: null, maintainerCount: null, publishedAt: null };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function getCratesMaintenance(name) {
|
|
131
|
+
try {
|
|
132
|
+
const data = await fetchJson(
|
|
133
|
+
`https://crates.io/api/v1/crates/${encodeURIComponent(name)}`,
|
|
134
|
+
{ "User-Agent": "KiroGraph/1.0" }
|
|
135
|
+
);
|
|
136
|
+
const crate = data["crate"];
|
|
137
|
+
const updatedAt = crate["updated_at"] ?? null;
|
|
138
|
+
const createdAt = crate["created_at"] ?? null;
|
|
139
|
+
const daysSinceLastCommit = updatedAt ? Math.floor((Date.now() - new Date(updatedAt).getTime()) / (1e3 * 60 * 60 * 24)) : null;
|
|
140
|
+
return { daysSinceLastCommit, maintainerCount: null, publishedAt: createdAt };
|
|
141
|
+
} catch {
|
|
142
|
+
return { daysSinceLastCommit: null, maintainerCount: null, publishedAt: null };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function getPypiMaintenance(name) {
|
|
146
|
+
try {
|
|
147
|
+
const data = await fetchJson(`https://pypi.org/pypi/${encodeURIComponent(name)}/json`);
|
|
148
|
+
const info = data["info"];
|
|
149
|
+
const releases = data["releases"] ?? {};
|
|
150
|
+
let latestMs = null;
|
|
151
|
+
let earliestMs = null;
|
|
152
|
+
for (const files of Object.values(releases)) {
|
|
153
|
+
for (const f of files) {
|
|
154
|
+
const t = f["upload_time_iso_8601"] ?? f["upload_time"];
|
|
155
|
+
if (t) {
|
|
156
|
+
const ms = new Date(t).getTime();
|
|
157
|
+
if (!isNaN(ms)) {
|
|
158
|
+
latestMs = latestMs === null ? ms : Math.max(latestMs, ms);
|
|
159
|
+
earliestMs = earliestMs === null ? ms : Math.min(earliestMs, ms);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const daysSinceLastCommit = latestMs !== null ? Math.floor((Date.now() - latestMs) / (1e3 * 60 * 60 * 24)) : null;
|
|
165
|
+
const maintainerCount = null;
|
|
166
|
+
const authorEmail = info["author_email"];
|
|
167
|
+
const estimatedMaintainers = authorEmail ? authorEmail.split(",").filter((s) => s.trim()).length : null;
|
|
168
|
+
const publishedAt = earliestMs !== null ? new Date(earliestMs).toISOString() : null;
|
|
169
|
+
return { daysSinceLastCommit, maintainerCount: estimatedMaintainers ?? maintainerCount, publishedAt };
|
|
170
|
+
} catch {
|
|
171
|
+
return { daysSinceLastCommit: null, maintainerCount: null, publishedAt: null };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function getMaintenance(ecosystem, name) {
|
|
175
|
+
const eco = ecosystem.toLowerCase();
|
|
176
|
+
switch (eco) {
|
|
177
|
+
case "npm":
|
|
178
|
+
return getNpmMaintenance(name);
|
|
179
|
+
case "cargo":
|
|
180
|
+
return getCratesMaintenance(name);
|
|
181
|
+
case "pypi":
|
|
182
|
+
case "pip":
|
|
183
|
+
return getPypiMaintenance(name);
|
|
184
|
+
default:
|
|
185
|
+
return { daysSinceLastCommit: null, maintainerCount: null, publishedAt: null };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function computeRisk(scorecardScore, daysSinceLastCommit, maintainerCount, isVeryNew, hasActiveCVEs) {
|
|
189
|
+
const reasons = [];
|
|
190
|
+
const isAbandoned = daysSinceLastCommit !== null && daysSinceLastCommit > 365;
|
|
191
|
+
const isSingleMaintainer = maintainerCount !== null && maintainerCount === 1;
|
|
192
|
+
const lowScorecard = scorecardScore !== null && scorecardScore < 3;
|
|
193
|
+
const mediumScorecard = scorecardScore !== null && scorecardScore >= 3 && scorecardScore <= 6;
|
|
194
|
+
const noActivityRecent = daysSinceLastCommit !== null && daysSinceLastCommit > 180;
|
|
195
|
+
if (isAbandoned) reasons.push(`Abandoned: no activity for ${daysSinceLastCommit} days`);
|
|
196
|
+
if (isVeryNew) reasons.push("Very new package (< 30 days old): potential supply chain risk");
|
|
197
|
+
if (isSingleMaintainer) reasons.push("Single maintainer: bus-factor risk");
|
|
198
|
+
if (lowScorecard) reasons.push(`Low OpenSSF Scorecard score: ${scorecardScore.toFixed(1)}/10`);
|
|
199
|
+
if (mediumScorecard) reasons.push(`Moderate OpenSSF Scorecard score: ${scorecardScore.toFixed(1)}/10`);
|
|
200
|
+
if (noActivityRecent && !isAbandoned) reasons.push(`No activity in ${daysSinceLastCommit} days`);
|
|
201
|
+
if (hasActiveCVEs) reasons.push("Has known CVEs");
|
|
202
|
+
let riskLevel;
|
|
203
|
+
if (isAbandoned && hasActiveCVEs) {
|
|
204
|
+
riskLevel = "critical";
|
|
205
|
+
} else if (isSingleMaintainer || lowScorecard || isVeryNew) {
|
|
206
|
+
riskLevel = "high";
|
|
207
|
+
} else if (mediumScorecard || noActivityRecent && !isAbandoned) {
|
|
208
|
+
riskLevel = "medium";
|
|
209
|
+
} else if (scorecardScore !== null && scorecardScore > 6) {
|
|
210
|
+
riskLevel = "low";
|
|
211
|
+
} else {
|
|
212
|
+
riskLevel = "unknown";
|
|
213
|
+
}
|
|
214
|
+
return { riskLevel, riskReasons: reasons };
|
|
215
|
+
}
|
|
216
|
+
class SupplyChainChecker {
|
|
217
|
+
constructor(db) {
|
|
218
|
+
this.db = db;
|
|
219
|
+
}
|
|
220
|
+
async checkAll() {
|
|
221
|
+
const rawDb = this.db.getRawDb();
|
|
222
|
+
const rows = rawDb.all(
|
|
223
|
+
`SELECT node_id, package_name, ecosystem FROM sec_dependencies`
|
|
224
|
+
);
|
|
225
|
+
const results = [];
|
|
226
|
+
const errors = [];
|
|
227
|
+
for (const row of rows) {
|
|
228
|
+
try {
|
|
229
|
+
const risk = await this._check(row.node_id, row.package_name, row.ecosystem);
|
|
230
|
+
if (risk) results.push(risk);
|
|
231
|
+
} catch (err) {
|
|
232
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
233
|
+
(0, import_errors.logWarn)(`[supply-chain] Failed to check ${row.package_name} (${row.ecosystem}): ${msg}`);
|
|
234
|
+
errors.push(`${row.package_name} (${row.ecosystem}): ${msg}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { results, errors };
|
|
238
|
+
}
|
|
239
|
+
async checkOne(packageName, ecosystem) {
|
|
240
|
+
const rawDb = this.db.getRawDb();
|
|
241
|
+
const row = rawDb.get(
|
|
242
|
+
`SELECT node_id FROM sec_dependencies WHERE package_name = ? AND ecosystem = ? LIMIT 1`,
|
|
243
|
+
[packageName, ecosystem]
|
|
244
|
+
);
|
|
245
|
+
if (!row) return null;
|
|
246
|
+
return this._check(row.node_id, packageName, ecosystem);
|
|
247
|
+
}
|
|
248
|
+
async _check(nodeId, packageName, ecosystem) {
|
|
249
|
+
const githubRepo = await resolveGithubRepo(ecosystem, packageName);
|
|
250
|
+
let scorecardScore = null;
|
|
251
|
+
let scorecardChecks = null;
|
|
252
|
+
if (githubRepo) {
|
|
253
|
+
const sc = await fetchScorecard(githubRepo);
|
|
254
|
+
if (sc) {
|
|
255
|
+
scorecardScore = sc.score;
|
|
256
|
+
scorecardChecks = sc.checks;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const maintenance = await getMaintenance(ecosystem, packageName);
|
|
260
|
+
const { daysSinceLastCommit, maintainerCount, publishedAt } = maintenance;
|
|
261
|
+
const isAbandoned = daysSinceLastCommit !== null && daysSinceLastCommit > 365;
|
|
262
|
+
const isSingleMaintainer = maintainerCount !== null && maintainerCount === 1;
|
|
263
|
+
const publishedMs = publishedAt ? new Date(publishedAt).getTime() : null;
|
|
264
|
+
const isVeryNew = publishedMs !== null && Date.now() - publishedMs < 30 * 24 * 60 * 60 * 1e3;
|
|
265
|
+
const rawDb = this.db.getRawDb();
|
|
266
|
+
const cveCount = rawDb.get(
|
|
267
|
+
`SELECT COUNT(*) as count FROM sec_vulnerabilities v
|
|
268
|
+
JOIN edges e ON e.target = v.node_id AND e.kind = 'has_vulnerability'
|
|
269
|
+
WHERE e.source = ?`,
|
|
270
|
+
[nodeId]
|
|
271
|
+
) ?? { count: 0 };
|
|
272
|
+
const hasActiveCVEs = cveCount.count > 0;
|
|
273
|
+
const { riskLevel, riskReasons } = computeRisk(
|
|
274
|
+
scorecardScore,
|
|
275
|
+
daysSinceLastCommit,
|
|
276
|
+
maintainerCount,
|
|
277
|
+
isVeryNew,
|
|
278
|
+
hasActiveCVEs
|
|
279
|
+
);
|
|
280
|
+
return {
|
|
281
|
+
packageName,
|
|
282
|
+
ecosystem,
|
|
283
|
+
nodeId,
|
|
284
|
+
scorecardScore,
|
|
285
|
+
scorecardChecks,
|
|
286
|
+
daysSinceLastCommit,
|
|
287
|
+
isAbandoned,
|
|
288
|
+
maintainerCount,
|
|
289
|
+
isSingleMaintainer,
|
|
290
|
+
publishedAt,
|
|
291
|
+
isVeryNew,
|
|
292
|
+
riskLevel,
|
|
293
|
+
riskReasons
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
298
|
+
0 && (module.exports = {
|
|
299
|
+
SupplyChainChecker
|
|
300
|
+
});
|
|
301
|
+
//# sourceMappingURL=supply-chain.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/security/supply-chain.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Supply Chain Health Checker\n *\n * Queries OpenSSF Scorecard and registry APIs to assess the health of\n * dependencies: maintenance activity, maintainer count, package age, and\n * overall supply-chain risk level.\n */\n\nimport { logWarn } from '../errors';\nimport type { GraphDatabase } from '../db/database';\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 SupplyChainRisk {\n packageName: string;\n ecosystem: string;\n nodeId: string;\n // OpenSSF Scorecard\n scorecardScore: number | null; // 0-10\n scorecardChecks: Record<string, number> | null;\n // Maintenance health\n daysSinceLastCommit: number | null;\n isAbandoned: boolean; // no commit in > 365 days\n maintainerCount: number | null;\n isSingleMaintainer: boolean;\n // Package age\n publishedAt: string | null;\n isVeryNew: boolean; // published < 30 days ago (supply chain risk)\n // Risk summary\n riskLevel: 'critical' | 'high' | 'medium' | 'low' | 'unknown';\n riskReasons: string[];\n}\n\n// \u2500\u2500 Fetch 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\nconst TIMEOUT_MS = 10_000;\n\nasync function fetchJson(url: string, headers?: Record<string, string>): Promise<unknown> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);\n try {\n const resp = await fetch(url, {\n signal: controller.signal,\n headers: { 'Accept': 'application/json', ...headers },\n });\n if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\n return await resp.json();\n } finally {\n clearTimeout(timer);\n }\n}\n\n// \u2500\u2500 Registry repo-URL resolvers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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\nasync function getGithubRepoFromNpm(name: string): Promise<string | null> {\n try {\n const encoded = encodeURIComponent(name);\n const data = await fetchJson(`https://registry.npmjs.org/${encoded}/latest`) as Record<string, unknown>;\n const repo = data['repository'] as Record<string, string> | string | undefined;\n const url = typeof repo === 'string' ? repo : repo?.url ?? null;\n return extractGithubPath(url);\n } catch {\n return null;\n }\n}\n\nasync function getGithubRepoFromCargo(name: string): Promise<string | null> {\n try {\n const data = await fetchJson(\n `https://crates.io/api/v1/crates/${encodeURIComponent(name)}`,\n { 'User-Agent': 'KiroGraph/1.0' },\n ) as Record<string, unknown>;\n const crate = data['crate'] as Record<string, unknown>;\n const repo = crate['repository'] as string | null | undefined;\n return extractGithubPath(repo ?? null);\n } catch {\n return null;\n }\n}\n\nasync function getGithubRepoFromPypi(name: string): Promise<string | null> {\n try {\n const data = await fetchJson(`https://pypi.org/pypi/${encodeURIComponent(name)}/json`) as Record<string, unknown>;\n const info = data['info'] as Record<string, unknown>;\n const projectUrls = info['project_urls'] as Record<string, string> | null | undefined;\n const homePage = info['home_page'] as string | null | undefined;\n const sourceUrl = projectUrls?.['Source'] ?? projectUrls?.['source'] ?? projectUrls?.['Homepage'] ?? homePage ?? null;\n return extractGithubPath(sourceUrl);\n } catch {\n return null;\n }\n}\n\nfunction extractGithubPath(url: string | null | undefined): string | null {\n if (!url) return null;\n // normalise git+https://, git://, ssh://git@github.com, etc.\n const cleaned = url\n .replace(/^git\\+/, '')\n .replace(/^git:\\/\\//, 'https://')\n .replace(/^ssh:\\/\\/git@/, 'https://')\n .replace(/^git@github\\.com:/, 'https://github.com/')\n .replace(/\\.git$/, '');\n const m = cleaned.match(/github\\.com[/:]([^/\\s]+\\/[^/\\s#?]+)/);\n if (!m) return null;\n return m[1]!;\n}\n\nasync function resolveGithubRepo(ecosystem: string, name: string): Promise<string | null> {\n const eco = ecosystem.toLowerCase();\n switch (eco) {\n case 'npm': return getGithubRepoFromNpm(name);\n case 'cargo': return getGithubRepoFromCargo(name);\n case 'pypi':\n case 'pip': return getGithubRepoFromPypi(name);\n default: return null;\n }\n}\n\n// \u2500\u2500 OpenSSF Scorecard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 ScorecardResult {\n score: number;\n checks: Record<string, number>;\n}\n\nasync function fetchScorecard(githubRepo: string): Promise<ScorecardResult | null> {\n try {\n const data = await fetchJson(`https://api.securityscorecards.dev/projects/github.com/${githubRepo}`) as Record<string, unknown>;\n const score = data['score'] as number;\n const checksRaw = (data['checks'] as Array<Record<string, unknown>>) ?? [];\n const checks: Record<string, number> = {};\n for (const c of checksRaw) {\n const checkName = c['name'] as string;\n const checkScore = c['score'] as number;\n if (checkName) checks[checkName] = checkScore ?? 0;\n }\n return { score, checks };\n } catch {\n return null;\n }\n}\n\n// \u2500\u2500 Maintenance data from registries \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 MaintenanceInfo {\n daysSinceLastCommit: number | null;\n maintainerCount: number | null;\n publishedAt: string | null;\n}\n\nasync function getNpmMaintenance(name: string): Promise<MaintenanceInfo> {\n try {\n const encoded = encodeURIComponent(name);\n const full = await fetchJson(`https://registry.npmjs.org/${encoded}`) as Record<string, unknown>;\n const time = (full['time'] as Record<string, string>) ?? {};\n const maintainers = (full['maintainers'] as unknown[]) ?? [];\n const maintainerCount = maintainers.length;\n\n // Latest publish date\n const timeValues = Object.entries(time)\n .filter(([k]) => k !== 'created' && k !== 'modified')\n .map(([, v]) => new Date(v).getTime())\n .filter(t => !isNaN(t));\n const lastPublishMs = timeValues.length > 0 ? Math.max(...timeValues) : null;\n const daysSinceLastCommit = lastPublishMs !== null\n ? Math.floor((Date.now() - lastPublishMs) / (1000 * 60 * 60 * 24))\n : null;\n\n // Earliest publish date as \"publishedAt\"\n const createdStr = time['created'] ?? null;\n const publishedAt = createdStr || null;\n\n return { daysSinceLastCommit, maintainerCount: maintainerCount || null, publishedAt };\n } catch {\n return { daysSinceLastCommit: null, maintainerCount: null, publishedAt: null };\n }\n}\n\nasync function getCratesMaintenance(name: string): Promise<MaintenanceInfo> {\n try {\n const data = await fetchJson(\n `https://crates.io/api/v1/crates/${encodeURIComponent(name)}`,\n { 'User-Agent': 'KiroGraph/1.0' },\n ) as Record<string, unknown>;\n const crate = data['crate'] as Record<string, unknown>;\n const updatedAt = (crate['updated_at'] as string) ?? null;\n const createdAt = (crate['created_at'] as string) ?? null;\n\n const daysSinceLastCommit = updatedAt\n ? Math.floor((Date.now() - new Date(updatedAt).getTime()) / (1000 * 60 * 60 * 24))\n : null;\n\n return { daysSinceLastCommit, maintainerCount: null, publishedAt: createdAt };\n } catch {\n return { daysSinceLastCommit: null, maintainerCount: null, publishedAt: null };\n }\n}\n\nasync function getPypiMaintenance(name: string): Promise<MaintenanceInfo> {\n try {\n const data = await fetchJson(`https://pypi.org/pypi/${encodeURIComponent(name)}/json`) as Record<string, unknown>;\n const info = data['info'] as Record<string, unknown>;\n const releases = (data['releases'] as Record<string, Array<Record<string, string>>>) ?? {};\n\n // Find the most recent release upload time across all versions\n let latestMs: number | null = null;\n let earliestMs: number | null = null;\n for (const files of Object.values(releases)) {\n for (const f of files) {\n const t = f['upload_time_iso_8601'] ?? f['upload_time'];\n if (t) {\n const ms = new Date(t).getTime();\n if (!isNaN(ms)) {\n latestMs = latestMs === null ? ms : Math.max(latestMs, ms);\n earliestMs = earliestMs === null ? ms : Math.min(earliestMs, ms);\n }\n }\n }\n }\n\n const daysSinceLastCommit = latestMs !== null\n ? Math.floor((Date.now() - latestMs) / (1000 * 60 * 60 * 24))\n : null;\n\n // PyPI doesn't expose maintainer counts in the public JSON\n const maintainerCount = null;\n const authorEmail = info['author_email'] as string | null | undefined;\n // Rough proxy: if the author_email field has multiple addresses \u2192 multiple maintainers\n const estimatedMaintainers = authorEmail\n ? authorEmail.split(',').filter(s => s.trim()).length\n : null;\n\n const publishedAt = earliestMs !== null ? new Date(earliestMs).toISOString() : null;\n\n return { daysSinceLastCommit, maintainerCount: estimatedMaintainers ?? maintainerCount, publishedAt };\n } catch {\n return { daysSinceLastCommit: null, maintainerCount: null, publishedAt: null };\n }\n}\n\nasync function getMaintenance(ecosystem: string, name: string): Promise<MaintenanceInfo> {\n const eco = ecosystem.toLowerCase();\n switch (eco) {\n case 'npm': return getNpmMaintenance(name);\n case 'cargo': return getCratesMaintenance(name);\n case 'pypi':\n case 'pip': return getPypiMaintenance(name);\n default: return { daysSinceLastCommit: null, maintainerCount: null, publishedAt: null };\n }\n}\n\n// \u2500\u2500 Risk scoring \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 computeRisk(\n scorecardScore: number | null,\n daysSinceLastCommit: number | null,\n maintainerCount: number | null,\n isVeryNew: boolean,\n hasActiveCVEs: boolean,\n): { riskLevel: SupplyChainRisk['riskLevel']; riskReasons: string[] } {\n const reasons: string[] = [];\n\n const isAbandoned = daysSinceLastCommit !== null && daysSinceLastCommit > 365;\n const isSingleMaintainer = maintainerCount !== null && maintainerCount === 1;\n const lowScorecard = scorecardScore !== null && scorecardScore < 3;\n const mediumScorecard = scorecardScore !== null && scorecardScore >= 3 && scorecardScore <= 6;\n const noActivityRecent = daysSinceLastCommit !== null && daysSinceLastCommit > 180;\n\n if (isAbandoned) reasons.push(`Abandoned: no activity for ${daysSinceLastCommit} days`);\n if (isVeryNew) reasons.push('Very new package (< 30 days old): potential supply chain risk');\n if (isSingleMaintainer) reasons.push('Single maintainer: bus-factor risk');\n if (lowScorecard) reasons.push(`Low OpenSSF Scorecard score: ${scorecardScore!.toFixed(1)}/10`);\n if (mediumScorecard) reasons.push(`Moderate OpenSSF Scorecard score: ${scorecardScore!.toFixed(1)}/10`);\n if (noActivityRecent && !isAbandoned) reasons.push(`No activity in ${daysSinceLastCommit} days`);\n if (hasActiveCVEs) reasons.push('Has known CVEs');\n\n let riskLevel: SupplyChainRisk['riskLevel'];\n if (isAbandoned && hasActiveCVEs) {\n riskLevel = 'critical';\n } else if (isSingleMaintainer || lowScorecard || isVeryNew) {\n riskLevel = 'high';\n } else if (mediumScorecard || (noActivityRecent && !isAbandoned)) {\n riskLevel = 'medium';\n } else if (scorecardScore !== null && scorecardScore > 6) {\n riskLevel = 'low';\n } else {\n riskLevel = 'unknown';\n }\n\n return { riskLevel, riskReasons: reasons };\n}\n\n// \u2500\u2500 SupplyChainChecker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 SupplyChainChecker {\n constructor(private readonly db: GraphDatabase) {}\n\n async checkAll(): Promise<{ results: SupplyChainRisk[]; errors: string[] }> {\n const rawDb = this.db.getRawDb();\n const rows: Array<{\n node_id: string;\n package_name: string;\n ecosystem: string;\n }> = rawDb.all(\n `SELECT node_id, package_name, ecosystem FROM sec_dependencies`,\n );\n\n const results: SupplyChainRisk[] = [];\n const errors: string[] = [];\n\n for (const row of rows) {\n try {\n const risk = await this._check(row.node_id, row.package_name, row.ecosystem);\n if (risk) results.push(risk);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logWarn(`[supply-chain] Failed to check ${row.package_name} (${row.ecosystem}): ${msg}`);\n errors.push(`${row.package_name} (${row.ecosystem}): ${msg}`);\n }\n }\n\n return { results, errors };\n }\n\n async checkOne(packageName: string, ecosystem: string): Promise<SupplyChainRisk | null> {\n const rawDb = this.db.getRawDb();\n const row: { node_id: string } | undefined = rawDb.get(\n `SELECT node_id FROM sec_dependencies WHERE package_name = ? AND ecosystem = ? LIMIT 1`,\n [packageName, ecosystem],\n );\n if (!row) return null;\n return this._check(row.node_id, packageName, ecosystem);\n }\n\n private async _check(\n nodeId: string,\n packageName: string,\n ecosystem: string,\n ): Promise<SupplyChainRisk | null> {\n // Resolve GitHub repo from registry metadata\n const githubRepo = await resolveGithubRepo(ecosystem, packageName);\n\n // Fetch Scorecard (only if we have a GitHub repo)\n let scorecardScore: number | null = null;\n let scorecardChecks: Record<string, number> | null = null;\n if (githubRepo) {\n const sc = await fetchScorecard(githubRepo);\n if (sc) {\n scorecardScore = sc.score;\n scorecardChecks = sc.checks;\n }\n }\n\n // Fetch maintenance info from registry\n const maintenance = await getMaintenance(ecosystem, packageName);\n const { daysSinceLastCommit, maintainerCount, publishedAt } = maintenance;\n\n const isAbandoned = daysSinceLastCommit !== null && daysSinceLastCommit > 365;\n const isSingleMaintainer = maintainerCount !== null && maintainerCount === 1;\n\n // Package age\n const publishedMs = publishedAt ? new Date(publishedAt).getTime() : null;\n const isVeryNew = publishedMs !== null && (Date.now() - publishedMs) < 30 * 24 * 60 * 60 * 1000;\n\n // Check if dep has known CVEs\n const rawDb = this.db.getRawDb();\n const cveCount: { count: number } = rawDb.get(\n `SELECT COUNT(*) as count FROM sec_vulnerabilities v\n JOIN edges e ON e.target = v.node_id AND e.kind = 'has_vulnerability'\n WHERE e.source = ?`,\n [nodeId],\n ) ?? { count: 0 };\n const hasActiveCVEs = cveCount.count > 0;\n\n const { riskLevel, riskReasons } = computeRisk(\n scorecardScore,\n daysSinceLastCommit,\n maintainerCount,\n isVeryNew,\n hasActiveCVEs,\n );\n\n return {\n packageName,\n ecosystem,\n nodeId,\n scorecardScore,\n scorecardChecks,\n daysSinceLastCommit,\n isAbandoned,\n maintainerCount,\n isSingleMaintainer,\n publishedAt,\n isVeryNew,\n riskLevel,\n riskReasons,\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
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 __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var suppressions_exports = {};
|
|
30
|
+
__export(suppressions_exports, {
|
|
31
|
+
SuppressionManager: () => SuppressionManager
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(suppressions_exports);
|
|
34
|
+
var fs = __toESM(require("fs"));
|
|
35
|
+
var path = __toESM(require("path"));
|
|
36
|
+
class SuppressionManager {
|
|
37
|
+
constructor(projectRoot) {
|
|
38
|
+
this.projectRoot = projectRoot;
|
|
39
|
+
}
|
|
40
|
+
get filePath() {
|
|
41
|
+
return path.join(this.projectRoot, ".kirograph", "security-suppressions.json");
|
|
42
|
+
}
|
|
43
|
+
load() {
|
|
44
|
+
if (!fs.existsSync(this.filePath)) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
let all;
|
|
48
|
+
try {
|
|
49
|
+
const raw = fs.readFileSync(this.filePath, "utf-8");
|
|
50
|
+
all = JSON.parse(raw);
|
|
51
|
+
} catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const now = /* @__PURE__ */ new Date();
|
|
55
|
+
const active = all.filter((s) => {
|
|
56
|
+
if (!s.expiresAt) return true;
|
|
57
|
+
return new Date(s.expiresAt) > now;
|
|
58
|
+
});
|
|
59
|
+
if (active.length !== all.length) {
|
|
60
|
+
this.save(active);
|
|
61
|
+
}
|
|
62
|
+
return active;
|
|
63
|
+
}
|
|
64
|
+
save(suppressions) {
|
|
65
|
+
const dir = path.dirname(this.filePath);
|
|
66
|
+
if (!fs.existsSync(dir)) {
|
|
67
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
fs.writeFileSync(this.filePath, JSON.stringify(suppressions, null, 2), "utf-8");
|
|
70
|
+
}
|
|
71
|
+
add(cveId, reason, expiresAt) {
|
|
72
|
+
const current = this.load();
|
|
73
|
+
const idx = current.findIndex((s) => s.cveId === cveId);
|
|
74
|
+
const entry = {
|
|
75
|
+
cveId,
|
|
76
|
+
suppressedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
77
|
+
...reason ? { reason } : {},
|
|
78
|
+
...expiresAt ? { expiresAt } : {}
|
|
79
|
+
};
|
|
80
|
+
if (idx >= 0) {
|
|
81
|
+
current[idx] = entry;
|
|
82
|
+
} else {
|
|
83
|
+
current.push(entry);
|
|
84
|
+
}
|
|
85
|
+
this.save(current);
|
|
86
|
+
}
|
|
87
|
+
remove(cveId) {
|
|
88
|
+
const current = this.load();
|
|
89
|
+
const idx = current.findIndex((s) => s.cveId === cveId);
|
|
90
|
+
if (idx < 0) return false;
|
|
91
|
+
current.splice(idx, 1);
|
|
92
|
+
this.save(current);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
isSuppressed(cveId) {
|
|
96
|
+
return this.load().some((s) => s.cveId === cveId);
|
|
97
|
+
}
|
|
98
|
+
getActive() {
|
|
99
|
+
return this.load();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
103
|
+
0 && (module.exports = {
|
|
104
|
+
SuppressionManager
|
|
105
|
+
});
|
|
106
|
+
//# sourceMappingURL=suppressions.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/security/suppressions.ts"],
|
|
4
|
+
"sourcesContent": ["import * as fs from 'fs';\nimport * as path from 'path';\n\nexport interface Suppression {\n cveId: string;\n reason?: string;\n suppressedAt: string; // ISO 8601\n expiresAt?: string; // ISO 8601, optional\n}\n\nexport class SuppressionManager {\n constructor(private readonly projectRoot: string) {}\n\n private get filePath(): string {\n return path.join(this.projectRoot, '.kirograph', 'security-suppressions.json');\n }\n\n load(): Suppression[] {\n if (!fs.existsSync(this.filePath)) {\n return [];\n }\n\n let all: Suppression[];\n try {\n const raw = fs.readFileSync(this.filePath, 'utf-8');\n all = JSON.parse(raw) as Suppression[];\n } catch {\n return [];\n }\n\n const now = new Date();\n const active = all.filter(s => {\n if (!s.expiresAt) return true;\n return new Date(s.expiresAt) > now;\n });\n\n // Silently remove expired entries by rewriting if any were filtered\n if (active.length !== all.length) {\n this.save(active);\n }\n\n return active;\n }\n\n save(suppressions: Suppression[]): void {\n const dir = path.dirname(this.filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(this.filePath, JSON.stringify(suppressions, null, 2), 'utf-8');\n }\n\n add(cveId: string, reason?: string, expiresAt?: string): void {\n const current = this.load();\n const idx = current.findIndex(s => s.cveId === cveId);\n\n const entry: Suppression = {\n cveId,\n suppressedAt: new Date().toISOString(),\n ...(reason ? { reason } : {}),\n ...(expiresAt ? { expiresAt } : {}),\n };\n\n if (idx >= 0) {\n current[idx] = entry;\n } else {\n current.push(entry);\n }\n\n this.save(current);\n }\n\n remove(cveId: string): boolean {\n const current = this.load();\n const idx = current.findIndex(s => s.cveId === cveId);\n if (idx < 0) return false;\n current.splice(idx, 1);\n this.save(current);\n return true;\n }\n\n isSuppressed(cveId: string): boolean {\n return this.load().some(s => s.cveId === cveId);\n }\n\n getActive(): Suppression[] {\n return this.load();\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAoB;AACpB,WAAsB;AASf,MAAM,mBAAmB;AAAA,EAC9B,YAA6B,aAAqB;AAArB;AAAA,EAAsB;AAAA,EAEnD,IAAY,WAAmB;AAC7B,WAAO,KAAK,KAAK,KAAK,aAAa,cAAc,4BAA4B;AAAA,EAC/E;AAAA,EAEA,OAAsB;AACpB,QAAI,CAAC,GAAG,WAAW,KAAK,QAAQ,GAAG;AACjC,aAAO,CAAC;AAAA,IACV;AAEA,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,GAAG,aAAa,KAAK,UAAU,OAAO;AAClD,YAAM,KAAK,MAAM,GAAG;AAAA,IACtB,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,SAAS,IAAI,OAAO,OAAK;AAC7B,UAAI,CAAC,EAAE,UAAW,QAAO;AACzB,aAAO,IAAI,KAAK,EAAE,SAAS,IAAI;AAAA,IACjC,CAAC;AAGD,QAAI,OAAO,WAAW,IAAI,QAAQ;AAChC,WAAK,KAAK,MAAM;AAAA,IAClB;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,KAAK,cAAmC;AACtC,UAAM,MAAM,KAAK,QAAQ,KAAK,QAAQ;AACtC,QAAI,CAAC,GAAG,WAAW,GAAG,GAAG;AACvB,SAAG,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,IACvC;AACA,OAAG,cAAc,KAAK,UAAU,KAAK,UAAU,cAAc,MAAM,CAAC,GAAG,OAAO;AAAA,EAChF;AAAA,EAEA,IAAI,OAAe,QAAiB,WAA0B;AAC5D,UAAM,UAAU,KAAK,KAAK;AAC1B,UAAM,MAAM,QAAQ,UAAU,OAAK,EAAE,UAAU,KAAK;AAEpD,UAAM,QAAqB;AAAA,MACzB;AAAA,MACA,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,MACrC,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,MAC3B,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,IACnC;AAEA,QAAI,OAAO,GAAG;AACZ,cAAQ,GAAG,IAAI;AAAA,IACjB,OAAO;AACL,cAAQ,KAAK,KAAK;AAAA,IACpB;AAEA,SAAK,KAAK,OAAO;AAAA,EACnB;AAAA,EAEA,OAAO,OAAwB;AAC7B,UAAM,UAAU,KAAK,KAAK;AAC1B,UAAM,MAAM,QAAQ,UAAU,OAAK,EAAE,UAAU,KAAK;AACpD,QAAI,MAAM,EAAG,QAAO;AACpB,YAAQ,OAAO,KAAK,CAAC;AACrB,SAAK,KAAK,OAAO;AACjB,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,OAAwB;AACnC,WAAO,KAAK,KAAK,EAAE,KAAK,OAAK,EAAE,UAAU,KAAK;AAAA,EAChD;AAAA,EAEA,YAA2B;AACzB,WAAO,KAAK,KAAK;AAAA,EACnB;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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 __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
var types_exports = {};
|
|
16
|
+
module.exports = __toCommonJS(types_exports);
|
|
17
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/security/types.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * KiroGraph-Sec Type Definitions\n *\n * Interfaces for the security analysis module including manifest parsing,\n * vulnerability detection, reachability analysis, and CycloneDX export.\n */\n\n// \u2500\u2500 Manifest Parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 ParsedDependency {\n name: string;\n declaredConstraint: string;\n resolvedVersion?: string; // from lock file\n scope: 'production' | 'development' | 'optional';\n ecosystem: string;\n sourceManifest: string; // relative path to declaring manifest\n license?: string; // SPDX expression or license name, if available in the manifest\n}\n\nexport interface ManifestParseResult {\n dependenciesCreated: number;\n manifestsParsed: number;\n warnings: Array<{ file: string; line?: number; message: string }>;\n errors: Array<{ file: string; message: string }>;\n}\n\n// \u2500\u2500 Dependency Graph Integration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 IntegrationResult {\n importsEdgesCreated: number;\n referencesEdgesCreated: number;\n}\n\nexport interface TransitiveResult {\n dependsOnEdgesCreated: number;\n incompleteNodes: string[]; // nodes marked with transitive_status=incomplete\n}\n\nexport interface CleanupResult {\n nodesRemoved: number;\n edgesRemoved: number;\n}\n\n// \u2500\u2500 Vulnerability Database \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 CVERecord {\n id: string; // e.g. \"CVE-2023-12345\"\n severity: number; // CVSS v3.1 base score\n affectedVersionRanges: VersionRange[];\n fixedVersion?: string;\n summary: string;\n}\n\nexport interface VersionRange {\n introduced?: string;\n fixed?: string;\n lastAffected?: string;\n}\n\nexport interface EnrichmentResult {\n vulnerabilitiesFound: number;\n dependenciesChecked: number;\n errors: Array<{ dependency: string; database: string; error: string }>;\n staleNodes: string[]; // nodes where vulnDataStale was set\n}\n\n// \u2500\u2500 Reachability Analysis \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 type ReachabilityVerdict = 'affected' | 'not_affected' | 'under_investigation';\n\nexport interface ReachabilityPath {\n entryPoint: string; // node ID of the entry point\n path: string[]; // ordered list of node IDs from entry to dependency\n}\n\nexport interface ReachabilityResult {\n verdict: ReachabilityVerdict;\n paths: ReachabilityPath[];\n unresolvedSymbols: string[]; // up to 50, for under_investigation\n reachingEntryPointCount: number;\n}\n\nexport interface ImpactSummary {\n affectedLayers: string[]; // e.g. ['api', 'service']\n affectedEntryPoints: string[]; // node IDs of reaching entry points\n distinctPathCount: number; // capped at 100\n}\n\n// \u2500\u2500 Security Pipeline \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 SecurityResult {\n manifestsDiscovered: number;\n dependenciesCreated: number;\n vulnerabilitiesFound: number;\n affectedCount: number;\n notAffectedCount: number;\n underInvestigationCount: number;\n duration: number;\n}\n\n// \u2500\u2500 CycloneDX SBOM Export \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 SBOMMetadata {\n toolName: string;\n toolVersion: string;\n timestamp: string; // ISO 8601 UTC\n projectName: string;\n}\n\nexport interface CycloneDXSBOM {\n bomFormat: 'CycloneDX';\n specVersion: '1.5';\n version: number;\n metadata: Record<string, unknown>;\n components: CycloneDXComponent[];\n dependencies: CycloneDXDependencyEntry[];\n}\n\nexport interface CycloneDXComponent {\n type: 'library';\n name: string;\n version: string;\n purl: string;\n scope?: 'required' | 'optional';\n properties?: Array<{ name: string; value: string }>;\n}\n\nexport interface CycloneDXDependencyEntry {\n ref: string;\n dependsOn?: string[];\n}\n\n// \u2500\u2500 CycloneDX VEX Export \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 CycloneDXVEX {\n bomFormat: 'CycloneDX';\n specVersion: '1.5';\n version: number;\n metadata: Record<string, unknown>;\n vulnerabilities: CycloneDXVulnerability[];\n}\n\nexport interface CycloneDXVulnerability {\n id: string;\n source?: { name: string; url?: string };\n ratings?: Array<{ score: number; severity?: string; method?: string }>;\n analysis?: {\n state: 'affected' | 'not_affected' | 'under_investigation';\n justification?: string;\n detail?: string;\n };\n affects?: Array<{ ref: string }>;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;AAAA;AAAA;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|